using ColorMine.ColorSpaces; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; using System.Windows.Media; using Tango.Core; using Tango.BL; using Tango.BL.Enumerations; using Tango.PMR.ColorLab; using Google.Protobuf; using System.Runtime.InteropServices; using Tango.PMR; using System.Diagnostics; using System.Windows.Threading; using System.Timers; using Tango.Core.Threading; namespace Tango.BL.Entities { public partial class BrushStop : BrushStopBase { private bool _ignorePropChanged; private ActionTimer _syncTimer; private static List _colorPropertyNames; public const double MAX_INK_UPTAKE = 400; private object _lock = new object(); #region Enums /// /// Represents brush stops color space synchronization mode. /// public enum ColorSynchronizationModes { /// /// No synchronization will be performed. /// None, /// /// Synchronizes changes immediately. /// Immediate, /// /// Throttles changes within 10 milliseconds. /// Throttle, } #endregion #region Constructors /// /// Initializes the class. /// static BrushStop() { _colorPropertyNames = new List(); _colorPropertyNames.Add(nameof(Red)); _colorPropertyNames.Add(nameof(Green)); _colorPropertyNames.Add(nameof(Blue)); _colorPropertyNames.Add(nameof(L)); _colorPropertyNames.Add(nameof(A)); _colorPropertyNames.Add(nameof(B)); _colorPropertyNames.Add(nameof(Cyan)); _colorPropertyNames.Add(nameof(Magenta)); _colorPropertyNames.Add(nameof(Yellow)); _colorPropertyNames.Add(nameof(Black)); _colorPropertyNames.Add(nameof(Color)); _colorPropertyNames.Add(nameof(ColorCatalogsItem)); _colorPropertyNames.Add(nameof(IsTransparent)); } /// /// Initializes a new instance of the class. /// public BrushStop() : base() { _syncTimer = new ActionTimer(TimeSpan.FromMilliseconds(10)); } #endregion #region Static Properties /// /// Gets or sets the color synchronization mode for all brush stops. /// public static ColorSynchronizationModes ColorSynchronizationMode { get; set; } = ColorSynchronizationModes.Throttle; #endregion #region Properties /// /// Gets or sets a value indicating whether the total value of liquid volumes has exceeded the maximum range of . /// [NotMapped] [JsonIgnore] public bool IsLiquidVolumesOutOfRange { get { return LiquidVolumes != null ? LiquidVolumes.Where(x => x.IdsPack.IdsPackFormula.Code == IdsPackFormulas.StandardColor.ToInt32()).Sum(x => x.NanoliterPerCentimeter) > GetTotalMaximumLiquidNlPerCMLimit() : false; } } private ObservableCollection _liquidVolumes; /// /// Gets or sets the collection of this liquid volumes. /// [NotMapped] [JsonIgnore] public ObservableCollection LiquidVolumes { get { return _liquidVolumes; } set { _liquidVolumes = value; RaisePropertyChangedAuto(); RaisePropertyChanged(nameof(LiquidVolumesOrdered)); RaisePropertyChanged(nameof(LiquidVolumesOrderedPigmented)); RaisePropertyChanged(nameof(LiquidVolumesOrderedPigmentedForStandardUser)); } } /// /// Gets the collection of liquid volumes ordered by their liquid type preferred index. /// [NotMapped] [JsonIgnore] public ObservableCollection LiquidVolumesOrdered { get { return LiquidVolumes != null ? LiquidVolumes.OrderBy(x => x.IdsPack.LiquidType.PreferredIndex).ToObservableCollection() : null; } } /// /// Gets the collection of liquid volumes with pigmented (color) liquid types ordered by their liquid type preferred index. /// [NotMapped] [JsonIgnore] public ObservableCollection LiquidVolumesOrderedPigmented { get { if (LiquidVolumes != null) { lock (_lock) { var list = LiquidVolumes.ToList().Where(x => x.IdsPack.LiquidType.HasPigment).OrderBy(x => x.IdsPack.LiquidType.PreferredIndex).ToObservableCollection(); return list; } } else { return null; } } } public void SetLiquidVolume(LiquidTypes liquidType, double volume) { LiquidVolumes.First(x => x.LiquidType == liquidType).Volume = volume; } /// /// Gets the collection of liquid volumes with pigmented (color) and available to standard user liquid types ordered by their liquid type preferred index. /// [NotMapped] [JsonIgnore] public ObservableCollection LiquidVolumesOrderedPigmentedForStandardUser { get { if (LiquidVolumes != null) { lock (_lock) { var list = LiquidVolumes.ToList().Where(x => x.IdsPack.LiquidType.HasPigment && x.IdsPack.LiquidType.AvailableForStandardUser).OrderBy(x => x.IdsPack.LiquidType.PreferredIndex).ToList().ToObservableCollection(); return list; } } else { return null; } } } /// /// Gets or sets the brush stop color. /// [NotMapped] [JsonIgnore] public Color Color { get { byte r = 0; byte g = 0; byte b = 0; if (Segment != null && Segment.Job != null) { if (Segment.Job.Version < 2 || (BestMatchR == null || BestMatchG == null || BestMatchB == null)) { r = (byte)Red; g = (byte)Green; b = (byte)Blue; } else { r = (byte)BestMatchR; g = (byte)BestMatchG; b = (byte)BestMatchB; } } else { r = (byte)Red; g = (byte)Green; b = (byte)Blue; } if (ColorSpace != null && BrushColorSpace == ColorSpaces.Catalog) { if (ColorCatalogsItem != null) { return Color.FromRgb((byte)ColorCatalogsItem.Red, (byte)ColorCatalogsItem.Green, (byte)ColorCatalogsItem.Blue); } else { return Color.FromRgb(r, g, b); } } else { return Color.FromRgb(r, g, b); } } set { Rgb rgb = new Rgb(value.R, value.G, value.B); _red = (byte)rgb.R; _green = (byte)rgb.G; _blue = (byte)rgb.B; Cmyk cmyk = rgb.To(); _cyan = cmyk.C; _magenta = cmyk.M; _yellow = cmyk.Y; _black = cmyk.K; Lab lab = rgb.ToLabUsingColorful(); _l = lab.L; _a = lab.A; _b = lab.B; RaisePropertyChanged(nameof(Color)); } } /// /// Returns a solid brush from . /// [NotMapped] [JsonIgnore] public SolidColorBrush Brush { get { return new SolidColorBrush(Color); } } /// /// Gets a value indicating whether this brush stop is the first one within its segment brush stops. /// [NotMapped] [JsonIgnore] public bool IsFirst { get { if (Segment != null && Segment.BrushStops.Count > 0) { return StopIndex == Segment.BrushStops.Min(x => x.StopIndex); } else { return true; } } } /// /// Gets a value indicating whether this brush stop is the last one within its segment brush stops. /// [NotMapped] [JsonIgnore] public bool IsLast { get { if (Segment != null && Segment.BrushStops.Count > 0) { return StopIndex == Segment.BrushStops.Max(x => x.StopIndex); } else { return true; } } } /// /// Gets a value indicating whether this brush stop is not the first nor last within its segment brush stops. /// [NotMapped] [JsonIgnore] public bool IsMiddle { get { return !IsFirst && !IsLast; } } /// /// Gets this brush stop offset in meters. /// [NotMapped] [JsonIgnore] public double OffsetMeters { get { if (Segment != null) { var a = Segment.Length * (OffsetPercent / 100d); return a; } else { return 0; } } set { if (Segment != null) { OffsetPercent = (value / Segment.Length) * 100d; RaisePropertyChangedAuto(); } } } private bool _isOutOfGamut; /// /// Gets or sets a value indicating whether this instance is out of gamut. /// [NotMapped] [JsonIgnore] public bool IsOutOfGamut { get { return _isOutOfGamut && BrushColorSpace != ColorSpaces.Volume && BrushColorSpace != ColorSpaces.Catalog; } set { if (_isOutOfGamut != value) { _isOutOfGamut = value; RaisePropertyChangedAuto(); if (Segment != null) { Segment.RaiseHasOutOfGamutBrushStop(); } } } } private bool _outOfGamutChecked; /// /// Gets or sets a value indicating whether out of gamut has been checked. /// [NotMapped] [JsonIgnore] public bool OutOfGamutChecked { get { return _outOfGamutChecked; } set { _outOfGamutChecked = value; RaisePropertyChangedAuto(); } } /// /// Gets the total liquid volume. /// [NotMapped] [JsonIgnore] public double TotalLiquidVolume { get { return LiquidVolumes != null ? LiquidVolumes.Where(x => x.IdsPack.IdsPackFormula.Code != IdsPackFormulas.Lubricant.ToInt32() && x.IdsPack.IdsPackFormula.Code != IdsPackFormulas.CleanerLiquid.ToInt32()).Sum(x => x.Volume) : 0; } } /// /// Gets the total liquid nanoliter per centimeter. /// [NotMapped] [JsonIgnore] public double TotalLiquidNanoliterPerCentimeter { get { return LiquidVolumes != null ? LiquidVolumes.Where(x => x.IdsPack.IdsPackFormula.Code != IdsPackFormulas.Lubricant.ToInt32() && x.IdsPack.IdsPackFormula.Code != IdsPackFormulas.CleanerLiquid.ToInt32()).Sum(x => x.NanoliterPerCentimeter) : 0; } } /// /// Gets the brush color space as enum. /// [NotMapped] [JsonIgnore] public ColorSpaces BrushColorSpace { get { return (ColorSpaces)ColorSpace.Code; } } [NotMapped] [JsonIgnore] public bool IsWhite { get { try { if (ColorSpace != null) { if (BrushColorSpace == ColorSpaces.RGB) { if (Red == 255 && Green == 255 && Blue == 255) { return true; } } else if (BrushColorSpace == ColorSpaces.LAB) { if (L == 100 && A == 0 && B == 0) { return true; } } } } catch { } return false; } } [NotMapped] [JsonIgnore] public String LiquidVolumesOrderedPigmentedString { get { if (LiquidVolumes != null) { try { return String.Join(", ", LiquidVolumesOrderedPigmentedForStandardUser.Select(x => x.Volume.ToString("0.0"))); } catch { return String.Empty; } } else { return String.Empty; } } } #endregion #region Public Methods /// /// Notifies about the offset percentage and offset meters changes. /// public void RaiseOffsetChanged() { RaisePropertyChanged(nameof(OffsetPercent)); RaisePropertyChanged(nameof(OffsetMeters)); RaisePropertyChanged(nameof(IsFirst)); RaisePropertyChanged(nameof(IsLast)); RaisePropertyChanged(nameof(IsMiddle)); } /// /// Sets the brush stop index without raising change event. /// /// The index. public void SetStopIndexNoRaise(int index) { _stopindex = index; } /// /// Raises a change notification for . /// public void RaiseStopIndex() { StopIndex = _stopindex; RaisePropertyChanged(nameof(StopIndex)); } public void SetColorSpaceSilent(ColorSpace space) { _colorspace = space; _colorspaceguid = space.Guid; } #endregion #region Liquid Volumes Methods /// /// Sets this brush stop liquid volumes. /// /// The configuration. /// The RML. /// The process parameters table. public void SetLiquidVolumes(Configuration configuration, Rml rml, ProcessParametersTable processParametersTable) { //lock (_lock) //{ LiquidVolumes = new ObservableCollection(); lock (_lock) { foreach (var idsPack in configuration.GetSupportedIdsPacks(rml)) { var liquidVolume = new LiquidVolume(configuration, idsPack, rml, processParametersTable, this); liquidVolume.VolumeChanged += LiquidVolume_VolumeChanged; LiquidVolumes.Add(liquidVolume); } } foreach (var volume in LiquidVolumes.ToList()) { volume.Invalidate(); } RaisePropertyChanged(nameof(LiquidVolumes)); RaisePropertyChanged(nameof(LiquidVolumesOrdered)); RaisePropertyChanged(nameof(LiquidVolumesOrderedPigmented)); RaisePropertyChanged(nameof(LiquidVolumesOrderedPigmentedForStandardUser)); //} } /// /// Gets the liquid volume value by the specified IDS pack index. /// /// Index of the pack. /// public double GetVolume(int packIndex) { return (double)typeof(BrushStop).GetProperty("V" + packIndex).GetValue(this); } /// /// Gets the liquid volume value by the specified IDS pack liquid type. /// /// Type of the liquid. /// public double GetVolume(LiquidTypes liquidType) { return GetVolume(Segment.Job.Machine.Configuration.NoneEmptyIdsPacks.SingleOrDefault(x => x.LiquidType.Code == liquidType.ToInt32()).PackIndex); } /// /// Sets the liquid volume value by the specified IDS pack index. /// /// Index of the pack. /// The volume. public void SetVolume(int packIndex, double volume) { typeof(BrushStop).GetProperty("V" + packIndex).SetValue(this, Math.Max(0, volume)); } /// /// Sets the liquid volume value by the specified IDS pack liquid type. /// /// Type of the liquid. /// The volume. public void SetVolume(LiquidTypes liquidType, double volume) { SetVolume(Segment.Job.Machine.Configuration.NoneEmptyIdsPacks.SingleOrDefault(x => x.LiquidType.Code == liquidType.ToInt32()).PackIndex, volume); } /// /// Gets the dispensing step division by the specified IDS pack index. /// /// Index of the pack. /// public int GetDispensingDivision(int packIndex) { return (int)typeof(BrushStop).GetProperty("V" + packIndex + "Div").GetValue(this); } /// /// Sets the dispensing step division by the specified IDS pack index. /// /// Index of the pack. /// The division. public void SetDispensingDivision(int packIndex, int division) { typeof(BrushStop).GetProperty("V" + packIndex + "Div").SetValue(this, division); } /// /// Sets all dispensing step divisions. /// /// The division. public void SetAllDispensingStepDivisions(Dispensing.DispenserStepDivisions division, Configuration configuration) { for (int i = 0; i < configuration.IdsPacks.Count; i++) { typeof(BrushStop).GetProperty("V" + i + "Div").SetValue(this, (int)division); } } #endregion #region Color Synchronization /// /// Synchronizes between the different brush stop color spaces. /// public void SynchronizeColorSpaces() { if (ColorSpace != null) { Rgb rgb = new Rgb(Red, Green, Blue); Cmyk cmyk = new Cmyk(Cyan, Magenta, Yellow, Black); Lab lab = new Lab(L, A, B); switch ((ColorSpaces)ColorSpace.Code) { case ColorSpaces.RGB: cmyk = rgb.To(); lab = rgb.ToLabUsingColorful(); break; case ColorSpaces.CMYK: rgb = cmyk.To(); lab = cmyk.To(); break; case ColorSpaces.LAB: rgb = lab.ToRgbUsingColorful(); cmyk = lab.To(); break; case ColorSpaces.Catalog: if (ColorCatalogsItem != null) { rgb = new Rgb(ColorCatalogsItem.Red, ColorCatalogsItem.Green, ColorCatalogsItem.Blue); lab = new Lab(ColorCatalogsItem.L, ColorCatalogsItem.A, ColorCatalogsItem.B); cmyk = new Cmyk(ColorCatalogsItem.Cyan, ColorCatalogsItem.Magenta, ColorCatalogsItem.Yellow, ColorCatalogsItem.Black); try { if (LiquidVolumes != null) { foreach (var liquidVolume in LiquidVolumesOrderedPigmentedForStandardUser.ToList()) { liquidVolume.Volume = ColorCatalogsItem.GetLiquidVolumeByName(liquidVolume.IdsPack.LiquidType.DisplayName); } } } catch (Exception ex) { Debug.WriteLine(ex); } } Validate(null); break; } _red = (int)rgb.R; _green = (int)rgb.G; _blue = (int)rgb.B; _cyan = cmyk.C; _magenta = cmyk.M; _yellow = cmyk.Y; _black = cmyk.K; _l = lab.L; _a = lab.A; _b = lab.B; } } /// /// Handles color spaces synchronization when properties changes. /// /// Name of the property. private void PerformColorSynchronization(String propName) { if (ColorSynchronizationMode != ColorSynchronizationModes.None && !_ignorePropChanged && propName != nameof(ColorSpace) && ColorSpace != null) { if (_colorPropertyNames.Contains(propName)) { if (ColorSynchronizationMode == ColorSynchronizationModes.Throttle) { _syncTimer.ResetReplace(() => { SynchronizeColorSpaces(); _ignorePropChanged = true; foreach (var prop in _colorPropertyNames) { RaisePropertyChanged(prop); } if (Segment != null) { Segment.RaiseSegmentBrushChanged(); } _ignorePropChanged = false; }); } else { SynchronizeColorSpaces(); _ignorePropChanged = true; foreach (var prop in _colorPropertyNames) { RaisePropertyChanged(prop); } if (Segment != null) { Segment.RaiseSegmentBrushChanged(); } } } } } #endregion #region Event Handlers /// /// Handles the liquid volumes event. /// private void LiquidVolume_VolumeChanged() { RaisePropertyChanged(nameof(IsLiquidVolumesOutOfRange)); RaisePropertyChanged(nameof(TotalLiquidVolume)); RaisePropertyChanged(nameof(TotalLiquidNanoliterPerCentimeter)); } #endregion #region Override Methods /// /// Raises the property changed event. /// /// Name of the property. protected override void RaisePropertyChanged(string propName) { base.RaisePropertyChanged(propName); PerformColorSynchronization(propName); } protected override void OnIsTransparentChanged(bool istransparent) { base.OnIsTransparentChanged(istransparent); RaisePropertyChanged(nameof(IsOutOfGamut)); OutOfGamutChecked = false; if (Segment != null) { Segment.RaiseHasOutOfGamutBrushStop(); } } #endregion #region Properties Changed /// /// Called when the OffsetPercent has changed. /// /// protected override void OnOffsetPercentChanged(double offsetpercent) { base.OnOffsetPercentChanged(offsetpercent); RaisePropertyChanged(nameof(OffsetMeters)); if (Segment != null) { Segment.RaiseSegmentBrushChanged(); } } /// /// Called when the ColorSpace has changed. /// /// protected override void OnColorSpaceChanged(ColorSpace colorspace) { base.OnColorSpaceChanged(colorspace); _ignorePropChanged = true; RaisePropertyChanged(nameof(Color)); RaisePropertyChanged(nameof(Brush)); RaisePropertyChanged(nameof(IsOutOfGamut)); Segment?.RaiseSegmentBrushChanged(); Segment?.RaiseHasOutOfGamutBrushStop(); _ignorePropChanged = false; } #endregion #region Cloning /// /// Clones this entity. /// /// public override BrushStop Clone() { BrushStop cloned = base.Clone(); return cloned; } public BrushStop CloneBrush() { //BrushStop cloned = new BrushStop(); BrushStop cloned = base.Clone(); cloned.StopIndex = StopIndex; cloned.ColorSpace = this.ColorSpace; cloned.Red = this.Red; cloned.Green = this.Green; cloned.Blue = this.Blue; cloned.L = this.L; cloned.A = this.A; cloned.B = this.B; cloned.Cyan = this.Cyan; cloned.Magenta = this.Magenta; cloned.Yellow = this.Yellow; cloned.Black = this.Black; cloned.Color = this.Color; cloned.BestMatchR = this.BestMatchR; cloned.BestMatchG = this.BestMatchG; cloned.BestMatchB = this.BestMatchB; cloned.ColorCatalogGuid = this.ColorCatalogGuid; cloned.ColorCatalogsItem = this.ColorCatalogsItem; cloned.ColorSpace = this.ColorSpace; cloned.OffsetMeters = this.OffsetMeters; cloned.OffsetPercent = this.OffsetPercent; cloned.IsOutOfGamut = this.IsOutOfGamut; cloned.OutOfGamutChecked = this.OutOfGamutChecked; cloned.IsTransparent = this.IsTransparent; cloned.Segment = this.Segment; cloned.Corrected = this.Corrected; if (LiquidVolumes != null && LiquidVolumes.Count > 0) { cloned.LiquidVolumes = LiquidVolumes.Select(x => x.Clone(cloned)).ToObservableCollection(); } return cloned; } /// /// Creates a cloned version of this brush stop including all its liquid volumes. /// /// The segment. /// public BrushStop Clone(Segment segment) { BrushStop cloned = base.Clone(); cloned.Segment = segment; cloned.ColorCatalogsItem = ColorCatalogsItem; cloned.ColorSpace = ColorSpace; cloned.SegmentGuid = segment.Guid; if (LiquidVolumes != null && LiquidVolumes.Count > 0) { cloned.LiquidVolumes = LiquidVolumes.Select(x => x.Clone(cloned)).ToObservableCollection(); } return cloned; } #endregion #region Validation /// /// Called when entity is validating. /// /// protected override void OnValidating(ObservablesContext context) { base.OnValidating(context); //if (BrushColorSpace == ColorSpaces.Catalog && ColorCatalogsItem == null) //{ // InsertError(nameof(ColorCatalogsItem), "Please specify a color code."); //} } #endregion #region Private Methods private double GetTotalMaximumLiquidNlPerCMLimit() { try { var tables = Segment.Job.Rml.GetActiveProcessGroup().ProcessParametersTables.OrderBy(x => x.TableIndex).ToList(); if (tables.Count > 0) { return tables.Max(x => x.MaxInkUptake) + 1; } else { return MAX_INK_UPTAKE + 1; } } catch { return MAX_INK_UPTAKE + 1; } } #endregion } }