using CapMachine.Core; using CapMachine.Wpf.Models.PPCalc; using CapMachine.Wpf.Models.Tag; using CapMachine.Wpf.PPCalculation; using ImTools; using MaterialDesignThemes.Wpf; using NLog; using NPOI.XWPF.UserModel; using Prism.Events; using Prism.Mvvm; using Prism.Services.Dialogs; using SixLabors.ImageSharp.ColorSpaces; using System.Collections.Generic; using System.Text; namespace CapMachine.Wpf.Services { /// /// 物性计算的服务 /// public class PPCService : BindableBase { /// /// 计算扫描 Task /// private static Task CalcTask { get; set; } private static int _scanLoopStarted; public ConfigService ConfigService { get; } private IEventAggregator _EventAggregator { get; set; } public DataRecordService DataRecordService { get; } public SysRunService SysRunServer { get; } public ILogService Logger { get; } public MachineRtDataService MachineRtDataService { get; } public IDialogService DialogService { get; } private readonly PPCSuperheatSubcoolCalculator _superheatSubcoolCalculator; private readonly PPCThermodynamicSixResultsCalculator _thermodynamicSixResultsCalculator; /// /// 标签中心 /// public TagManager TagManager { get; set; } /// /// 实例化 /// public PPCService(ConfigService configService, IEventAggregator eventAggregator, DataRecordService dataRecordService, SysRunService sysRunService, ILogService logService, MachineRtDataService machineRtDataService, IDialogService dialogService ) { ConfigService = configService; //事件服务 _EventAggregator = eventAggregator; DataRecordService = dataRecordService; SysRunServer = sysRunService; Logger = logService; MachineRtDataService = machineRtDataService; DialogService = dialogService; TagManager = MachineRtDataService.TagManger; SpeedTag = TagManager.DicTags.GetValueOrDefault("转速[rpm]"); ExPressTag = TagManager.DicTags.GetValueOrDefault("排气压力[BarA]"); ExTempTag = TagManager.DicTags.GetValueOrDefault("排气温度[℃]"); HVPwTag = TagManager.DicTags.GetValueOrDefault("HV[W]"); InhPressTag = TagManager.DicTags.GetValueOrDefault("吸气压力[BarA]"); InhTempTag = TagManager.DicTags.GetValueOrDefault("吸气温度[℃]"); //InhTempTag = TagManager.DicTags.GetValueOrDefault("吸气温度[℃]")!; //ComCapBusVolTag = TagManager.DicTags.GetValueOrDefault("通讯母线电压[V]"); //ComCapBusCurTag = TagManager.DicTags.GetValueOrDefault("通讯母线电流[A]"); //ComCapPwTag = TagManager.DicTags.GetValueOrDefault("通讯功率[W]"); //OS2TempTag = TagManager.DicTags.GetValueOrDefault("吸气混合器温度[℃]"); //TxvFrTempTag = TagManager.DicTags.GetValueOrDefault("膨胀阀前温度[℃]")!; //TxvFrPressTag = TagManager.DicTags.GetValueOrDefault("膨胀阀前压力[BarA]")!; TxvFrTempTag = TagManager.DicTags.GetValueOrDefault("膨胀阀前温度[℃]"); TxvFrPressTag = TagManager.DicTags.GetValueOrDefault("膨胀阀前压力[BarA]"); LiqRefFlowTag = TagManager.DicTags.GetValueOrDefault("液冷媒流量[kg/h]"); //kg/h VRVTag = TagManager.DicTags.GetValueOrDefault("冷媒流量[kg/h]"); //润滑油流量 LubeFlowTag = TagManager.DicTags.GetValueOrDefault("润滑油流量[kg/h]"); //Cond1TempTag = TagManager.DicTags.GetValueOrDefault("冷凝器出口水温[℃]"); //CondInTempTag = TagManager.DicTags.GetValueOrDefault("冷凝器进口温度[℃]"); //Superheat = TagManager.DicTags.GetValueOrDefault("过热度[K]"); //Subcool = TagManager.DicTags.GetValueOrDefault("过冷度[K]"); Superheat = TagManager.DicTags.GetValueOrDefault("过热度[K]"); Subcool = TagManager.DicTags.GetValueOrDefault("过冷度[K]"); HeatingCapacity = TagManager.DicTags.GetValueOrDefault("制热量Qh[W]"); COPHeat = TagManager.DicTags.GetValueOrDefault("压缩机性能系数(制热)"); IsentrpEff = TagManager.DicTags.GetValueOrDefault("等熵效率ns[%]"); CoolCapacity = TagManager.DicTags.GetValueOrDefault("制冷量Qc[W]"); COPCool = TagManager.DicTags.GetValueOrDefault("压缩机性能系数(制冷)"); VoltricEff = TagManager.DicTags.GetValueOrDefault("容积效率nv[%]"); SuperHeatCoolConfig.FluidsPath = ConfigHelper.GetValue("FluidsPath"); SuperHeatCoolConfig.Cryogen = ConfigHelper.GetValue("Cryogen"); _superheatSubcoolCalculator = new PPCSuperheatSubcoolCalculator(); _thermodynamicSixResultsCalculator = new PPCThermodynamicSixResultsCalculator(); RtScanDeviceStart(); } /// /// 当前的配置 /// public SuperHeatCoolConfigModel SuperHeatCoolConfig { get; set; } = new SuperHeatCoolConfigModel(); /// /// 保存配置信息 /// public void SaveSuperHeatCoolConfig() { ConfigHelper.SetValue("FluidsPath", SuperHeatCoolConfig.FluidsPath); ConfigHelper.SetValue("Cryogen", SuperHeatCoolConfig.Cryogen); } /// /// 吸气压力 /// public ITag InhPressTag { get; set; } /// /// 转速标签 /// public ITag? SpeedTag { get; set; } /// /// 排气压力 /// public ITag? ExPressTag { get; set; } /// /// 排气温度 /// public ITag? ExTempTag { get; set; } /// /// 压缩机功率(HV 电源) /// public ITag? HVPwTag { get; set; } /// /// 吸气温度 /// public ITag InhTempTag { get; set; } /// /// 液体阀前温度 /// public ITag TxvFrTempTag { get; set; } /// /// 液体阀前压力 /// public ITag TxvFrPressTag { get; set; } /// /// 过热度 /// public ITag Superheat { get; set; } /// /// 过冷度 /// public ITag Subcool { get; set; } /// /// 干度(无量纲 [-]) /// public ITag DrynessTag { get; set; } private double _DrynessTag2Value; /// /// 干度2(无量纲 [-]) /// public double DrynessTag2Value { get { return _DrynessTag2Value; } set { _DrynessTag2Value = value; RaisePropertyChanged(); } } /// /// 气路阀前压力(BarA) /// public ITag GasPreValvePressTag { get; set; } /// /// 气路阀前温度(℃) /// public ITag GasPreValveTempTag { get; set; } /// /// 冷媒流量kg/h /// public ITag VRVTag { get; set; } /// /// 液体流量(kg/h) /// 液冷媒流量kg/h=液体流量kg/h /// public ITag LiqRefFlowTag { get; set; } /// /// 润滑油流量(kg/h) /// public ITag LubeFlowTag { get; set; } public ITag HeatingCapacity { get; set; } public ITag COPHeat { get; set; } public ITag IsentrpEff { get; set; } public ITag CoolCapacity { get; set; } public ITag COPCool { get; set; } public ITag VoltricEff { get; set; } /// /// 风量数据-乘以系数的后的最终结果 /// private double AirVolumeData { get; set; } = 0.0; /// /// 风量数据-公式计算的原始数据 /// private double AirVolumeDataSource { get; set; } = 0; /// /// 风量喷嘴启用信息数据 /// private string RozzleEnableInfo { get; set; } = ""; /// /// 启用计算 /// private bool RtCalcEnable { get; set; } = true; /// /// 触发日志 /// private bool DebugLog { get; set; } = false; /// /// PLC扫描线程 /// private void RtScanDeviceStart() { if (System.Threading.Interlocked.CompareExchange(ref _scanLoopStarted, 1, 0) != 0) { return; } CalcTask = Task.Run(async () => { while (RtCalcEnable) { await Task.Delay(1000); try { if (!TryUpdateSuperheatAndSubcool(out var superheatSubcoolErr)) { if (!string.IsNullOrWhiteSpace(superheatSubcoolErr)) { Logger?.Error($"过热度/过冷度计算失败: {superheatSubcoolErr}"); } } if (TryUpdateThermodynamicSixResults(out var thermoErr)) { if (!string.IsNullOrWhiteSpace(thermoErr)) { Logger?.Error($"六个物性结果计算警告: {thermoErr}"); } } else { if (!string.IsNullOrWhiteSpace(thermoErr)) { Logger?.Error($"六个物性结果计算失败: {thermoErr}"); } } } catch (Exception ex) { Logger.Error(String.Format("ErrSource : {0} ErrMsg : {1}", ex.StackTrace.ToString(), ex.Message.ToString())); } } }); } /// /// 更新过热度与过冷度结果。 /// /// /// 错误汇总输出。 /// 当两个结果都失败时,返回拼接后的失败原因; /// 当仅一个结果失败时,只返回该项失败原因; /// 当至少有一项成功更新时,方法返回 。 /// /// /// 是否至少成功更新了一个结果。 /// 该方法保持原有行为:过热度和过冷度彼此独立,只要其中之一成功就返回 。 /// private bool TryUpdateSuperheatAndSubcool(out string error) { error = string.Empty; bool updated = false; StringBuilder errorBuilder = new StringBuilder(); // 第一段:收集过热度所需标签,并把实时值直接交给独立计算类。 // PPCService 只负责“取值 + 回写”,具体热力学过程由 PPCSuperheatSubcoolCalculator 承担。 if (InhPressTag == null || InhTempTag == null || Superheat == null) { AppendCalculationError(errorBuilder, "缺少过热度计算标签"); } else if (_superheatSubcoolCalculator.TryCalculateSuperheatK(InhPressTag.EngPvValue, InhTempTag.EngPvValue, out var superheatValue, out var superheatErr)) { Superheat.EngPvValue =Math.Abs(superheatValue) ; updated = true; } else { AppendCalculationError(errorBuilder, superheatErr); } // 第二段:收集过冷度所需标签,并按同样方式委托给独立计算类。 // 两个结果互不阻塞,保持与旧实现一致的“部分成功也可回写”的策略。 if (TxvFrPressTag == null || TxvFrTempTag == null || Subcool == null) { AppendCalculationError(errorBuilder, "缺少过冷度计算标签"); } else if (_superheatSubcoolCalculator.TryCalculateSubcoolK(TxvFrPressTag.EngPvValue, TxvFrTempTag.EngPvValue, out var subcoolValue, out var subcoolErr)) { Subcool.EngPvValue = Math.Abs(subcoolValue); updated = true; } else { AppendCalculationError(errorBuilder, subcoolErr); } error = errorBuilder.ToString(); return updated; } private static void AppendCalculationError(StringBuilder errorBuilder, string error) { if (string.IsNullOrWhiteSpace(error)) { return; } if (errorBuilder.Length > 0) { errorBuilder.Append(";"); } errorBuilder.Append(error); } /// /// 按图片的最终流程计算干度: /// 1) 质量流量加权混合焓 h_mix = (h_vap*mg + h_liq*ml) / (mg + ml) /// 2) 干度 x = (h_mix - h_l) / (h_v - h_l),并限幅到 [0,1] /// /// 入参单位: /// - hVap_kJkg, hLiq_kJkg, hSatL_kJkg, hSatV_kJkg 均为 kJ/kg /// - mGas_kg_h, mLiq_kg_h 均为 kg/h /// 返回:true 表示成功,输出 x∈[0,1] 与 h_mix;false 返回 error /// /// 气相质量焓 h_vap [kJ/kg] /// 液相质量焓 h_liq [kJ/kg] /// 气体质量流量 mg [kg/h] /// 液体质量流量 ml [kg/h] /// 饱和液质量焓 h_l [kJ/kg] /// 饱和气质量焓 h_v [kJ/kg] /// 输出干度 x ∈ [0,1] /// 输出混合后总比焓 h_mix [kJ/kg] /// Err /// public bool TryComputeDrynessByEnthalpy( double hVap_kJkg, // 气相质量焓 h_vap [kJ/kg] double hLiq_kJkg, // 液相质量焓 h_liq [kJ/kg] double mGas_kg_h, // 气体质量流量 mg [kg/h] double mLiq_kg_h, // 液体质量流量 ml [kg/h] double hSatL_kJkg, // 饱和液质量焓 h_l [kJ/kg] double hSatV_kJkg, // 饱和气质量焓 h_v [kJ/kg] out double dryness, // 输出干度 x ∈ [0,1] out double hMix_kJkg, // 输出混合后总比焓 h_mix [kJ/kg] out string error) { dryness = double.NaN; hMix_kJkg = double.NaN; error = string.Empty; // 1) 合法性校验 if (double.IsNaN(hVap_kJkg) || double.IsNaN(hLiq_kJkg) || double.IsNaN(hSatL_kJkg) || double.IsNaN(hSatV_kJkg)) { error = "输入焓值存在 NaN"; return false; } if (double.IsNaN(mGas_kg_h) || double.IsNaN(mLiq_kg_h)) { error = "输入质量流量存在 NaN"; return false; } // 负值处理:小于 0 视为 0(避免传感器噪声或符号错误影响) double mg = Math.Max(0.0, mGas_kg_h); double ml = Math.Max(0.0, mLiq_kg_h); double mSum = mg + ml; if (mSum <= 0) { error = "气液质量流量之和为 0,无法进行加权混合焓计算"; return false; } // 2) 质量流量加权混合焓(严格按图片:上、下两路相加后除以总流量) hMix_kJkg = (hVap_kJkg * mg + hLiq_kJkg * ml) / mSum; // 3) 干度计算:x = (h_mix - h_l) / (h_v - h_l) double denom = (hSatV_kJkg - hSatL_kJkg); const double eps = 1e-9; if (Math.Abs(denom) < eps) { error = "饱和气/液焓差过小,无法计算干度(可能接近临界点或输入异常)"; return false; } double x = (hMix_kJkg - hSatL_kJkg) / denom; // 4) 限幅到 [0,1] if (double.IsNaN(x) || double.IsInfinity(x)) { error = "干度计算结果异常(NaN/Inf)"; return false; } dryness = Math.Min(1.0, Math.Max(0.0, x)); return true; } /// /// 按图片的最终流程计算干度2: /// 干度2的计算:临时用作为对比使用 /// 1) 质量流量加权混合焓 h_mix = (h_vap*mg + h_liq*ml) / (mg + ml) /// 2) 干度 x = (h_mix - h_l) / (h_v - h_l),并限幅到 [0,1] /// /// 入参单位: /// - hVap_kJkg, hLiq_kJkg, hSatL_kJkg, hSatV_kJkg 均为 kJ/kg /// - mGas_kg_h, mLiq_kg_h 均为 kg/h /// 返回:true 表示成功,输出 x∈[0,1] 与 h_mix;false 返回 error /// /// 气相质量焓 h_vap [kJ/kg] /// 液相质量焓 h_liq [kJ/kg] /// 气体质量流量 mg [kg/h] /// 液体质量流量 ml [kg/h] /// 饱和液质量焓 h_l [kJ/kg] /// 饱和气质量焓 h_v [kJ/kg] /// 输出干度 x ∈ [0,1] /// 输出混合后总比焓 h_mix [kJ/kg] /// Err /// public bool TryComputeDrynessByEnthalpy2( double hVap_kJkg, // 气相质量焓 h_vap [kJ/kg] double hLiq_kJkg, // 液相质量焓 h_liq [kJ/kg] double mGas_kg_h, // 气体质量流量 mg [kg/h] double lubeFlow_kg_h, // 润滑油流量 mg [kg/h] double mLiq_kg_h, // 液体质量流量 ml [kg/h] double hSatL_kJkg, // 饱和液质量焓 h_l [kJ/kg] double hSatV_kJkg, // 饱和气质量焓 h_v [kJ/kg] out double dryness, // 输出干度 x ∈ [0,1] out double hMix_kJkg, // 输出混合后总比焓 h_mix [kJ/kg] out string error) { dryness = double.NaN; hMix_kJkg = double.NaN; error = string.Empty; // 1) 合法性校验 if (double.IsNaN(hVap_kJkg) || double.IsNaN(hLiq_kJkg) || double.IsNaN(hSatL_kJkg) || double.IsNaN(hSatV_kJkg)) { error = "输入焓值存在 NaN"; return false; } if (double.IsNaN(mGas_kg_h) || double.IsNaN(mLiq_kg_h)) { error = "输入质量流量存在 NaN"; return false; } // 负值处理:小于 0 视为 0(避免传感器噪声或符号错误影响) //double mg1 = Math.Max(0.0, mGas_kg_h); double mg = Math.Max(0.0, mGas_kg_h) + Math.Max(0.0, lubeFlow_kg_h); // 这个是改动 气体流量再加上润滑油流量 double ml = Math.Max(0.0, mLiq_kg_h); double mSum = mg + ml; if (mSum <= 0) { error = "气液质量流量之和为 0,无法进行加权混合焓计算"; return false; } // 2) 质量流量加权混合焓(严格按图片:上、下两路相加后除以总流量) hMix_kJkg = (hVap_kJkg * mg + hLiq_kJkg * ml) / mSum; // 3) 干度计算:x = (h_mix - h_l) / (h_v - h_l) double denom = (hSatV_kJkg - hSatL_kJkg); const double eps = 1e-9; if (Math.Abs(denom) < eps) { error = "饱和气/液焓差过小,无法计算干度(可能接近临界点或输入异常)"; return false; } double x = (hMix_kJkg - hSatL_kJkg) / denom; // 4) 限幅到 [0,1] if (double.IsNaN(x) || double.IsInfinity(x)) { error = "干度计算结果异常(NaN/Inf)"; return false; } dryness = Math.Min(1.0, Math.Max(0.0, x)); return true; } ///制热量、压缩机性能系数COP(制热)、等熵效率、制冷量、压缩机性能系数COP(制冷)、容积效率 计算 #region private double _HeatingCapacityQh_kW; /// /// 制热量 Qh [kW] /// public double HeatingCapacityQh_kW { get { return _HeatingCapacityQh_kW; } set { _HeatingCapacityQh_kW = value; RaisePropertyChanged(); } } private double _COPHeating; /// /// 压缩机性能系数 COP(制热)[-] /// public double COPHeating { get { return _COPHeating; } set { _COPHeating = value; RaisePropertyChanged(); } } private double _IsentropicEfficiencyPct; /// /// 等熵效率 ηs [%] /// public double IsentropicEfficiencyPct { get { return _IsentropicEfficiencyPct; } set { _IsentropicEfficiencyPct = value; RaisePropertyChanged(); } } private double _CoolingCapacityQc_kW; /// /// 制冷量 Qc [kW] /// public double CoolingCapacityQc_kW { get { return _CoolingCapacityQc_kW; } set { _CoolingCapacityQc_kW = value; RaisePropertyChanged(); } } private double _COPCooling; /// /// 压缩机性能系数 COP(制冷)[-] /// public double COPCooling { get { return _COPCooling; } set { _COPCooling = value; RaisePropertyChanged(); } } private double _VolumetricEfficiencyPct; /// /// 容积效率 ηv [%] /// public double VolumetricEfficiencyPct { get { return _VolumetricEfficiencyPct; } set { _VolumetricEfficiencyPct = value; RaisePropertyChanged(); } } /// /// 按流程图更新:制热量、COP(制热)、等熵效率、制冷量、COP(制冷)、容积效率。 /// /// /// 错误/警告信息输出。 /// - 当方法返回 时, 为失败原因,调用方应视为本周期计算无效。 /// - 当方法返回 非空时,表示仅部分结果无法计算(例如缺少排量导致容积效率为 NaN)。 /// /// /// 是否成功完成本周期的结果更新。 /// - :至少已成功更新 Qh/Qc/COP/ηs 等主要结果;容积效率可能因缺失排量而为 NaN。 /// - :关键输入或 REFPROP 计算失败,本周期结果不更新。 /// private bool TryUpdateThermodynamicSixResults(out string error) { error = string.Empty; // 先把本周期所需的标签值组装为输入模型。 // PPCService 在这里负责完成“标签 -> 领域输入对象”的映射, // 从而把计算类和 UI/标签层解耦。 if (!TryCreateThermodynamicSixResultsInput(out var input, out var inputErr)) { error = inputErr; return false; } // 把输入模型交给独立计算类统一完成 6 个结果的热力计算。 // 此处不再在 PPCService 中展开公式,只保留流程调度职责。 if (!_thermodynamicSixResultsCalculator.TryCalculate(input, out var result, out error)) { return false; } // 先把结果同步到服务属性,供界面绑定或其他业务读取。 HeatingCapacityQh_kW = result.HeatingCapacityQh_kW; CoolingCapacityQc_kW = result.CoolingCapacityQc_kW; COPHeating = result.COPHeating; COPCooling = result.COPCooling; IsentropicEfficiencyPct = result.IsentropicEfficiencyPct; // 再把结果回写到对应工程量标签。 HeatingCapacity.EngPvValue = HeatingCapacityQh_kW; COPHeat.EngPvValue = COPHeating; IsentrpEff.EngPvValue = IsentropicEfficiencyPct; CoolCapacity.EngPvValue = CoolingCapacityQc_kW; COPCool.EngPvValue = COPCooling; // 容积效率在旧逻辑中允许单独失败; // 因此只有拿到有效数值时才回写,避免覆盖已有结果为 NaN/Inf。 if (!double.IsNaN(result.VolumetricEfficiencyPct) && !double.IsInfinity(result.VolumetricEfficiencyPct)) { VolumetricEfficiencyPct = result.VolumetricEfficiencyPct; VoltricEff.EngPvValue = VolumetricEfficiencyPct; } return true; } /// /// 创建六个热力结果值计算所需的输入模型。 /// /// /// 输出输入模型。 /// 该对象是 之间的数据边界: /// 由服务层负责从标签提取实时值,由计算层只消费该对象而不直接依赖标签。 /// /// 失败原因,通常为缺少标签、缺少排量配置或关键输入不可用。 /// 是否创建成功。 private bool TryCreateThermodynamicSixResultsInput(out PPCThermodynamicSixResultsCalculationInput input, out string error) { input = new PPCThermodynamicSixResultsCalculationInput(); error = string.Empty; // 依次校验本周期计算所需的全部标签。 // 这里不做公式计算,只保证独立计算类拿到的是一套完整输入。 if (HVPwTag == null) { error = "缺少 HV[W] 功率标签"; return false; } if (VRVTag == null) { error = "缺少总流量(冷媒流量)标签"; return false; } if (LubeFlowTag == null) { error = "缺少油流量标签"; return false; } if (InhPressTag == null || InhTempTag == null) { error = "缺少吸气压力/吸气温度标签"; return false; } if (ExPressTag == null || ExTempTag == null) { error = "缺少排气压力/排气温度标签"; return false; } if (TxvFrPressTag == null || TxvFrTempTag == null) { error = "缺少膨胀阀前压力/膨胀阀前温度标签"; return false; } if (SpeedTag == null) { error = "缺少转速标签"; return false; } if (!TryGetCompressorDisplacement_cc(out var displacementCc, out var displacementErr)) { error = displacementErr; return false; } // 将实时标签值与配置值组装为独立计算类可直接消费的输入对象。 input = new PPCThermodynamicSixResultsCalculationInput { CompressorPowerW = HVPwTag.EngPvValue, TotalMassFlowKgPerHour = VRVTag.EngPvValue, OilMassFlowKgPerHour = LubeFlowTag.EngPvValue, SuctionPressureBarA = InhPressTag.EngPvValue, SuctionTemperatureC = InhTempTag.EngPvValue, DischargePressureBarA = ExPressTag.EngPvValue, DischargeTemperatureC = ExTempTag.EngPvValue, LiquidPressureBarA = TxvFrPressTag.EngPvValue, LiquidTemperatureC = TxvFrTempTag.EngPvValue, CompressorSpeedRpm = SpeedTag.EngPvValue, CompressorDisplacementCc = displacementCc }; return true; } /// /// 获取压缩机排量。 /// /// 排量输出,单位 cc(cm³/rev)。 /// 失败原因(未配置、解析失败、数值不合法)。 /// 是否获取成功。 private bool TryGetCompressorDisplacement_cc(out double displacement_cc, out string error) { displacement_cc = double.NaN; error = string.Empty; displacement_cc = 35; return true; const string key = "CompressorDisplacementCc"; //if (!ConfigHelper.IsExist(key)) //{ // error = $"未配置压缩机排量,请在 App.config/appSettings 增加 {key}(单位 cc)"; // return false; //} string v = ConfigHelper.GetValue(key); if (!double.TryParse(v, out displacement_cc)) { error = $"压缩机排量配置无法解析: {v}"; return false; } if (displacement_cc <= 0) { error = $"压缩机排量<=0: {displacement_cc}"; return false; } return true; } #endregion } }