diff --git a/PamhagenSysCtrl/Helpers/PamhagenBrushes.cs b/PamhagenSysCtrl/Helpers/PamhagenBrushes.cs index ffc87ea..8df9855 100644 --- a/PamhagenSysCtrl/Helpers/PamhagenBrushes.cs +++ b/PamhagenSysCtrl/Helpers/PamhagenBrushes.cs @@ -5,10 +5,12 @@ namespace PamhagenSysCtrl.Helpers { public const double TRANSITION = 1.0 / 4096; - public static readonly SolidColorBrush Green = new SolidColorBrush(Color.FromRgb(0x20, 0xE0, 0x20)); - public static readonly SolidColorBrush DimOrange = new SolidColorBrush(Color.FromRgb(0xFF, 0xC0, 0x80)); - public static readonly SolidColorBrush Yellow = new SolidColorBrush(Color.FromRgb(0xFF, 0xE0, 0x10)); - public static readonly SolidColorBrush Red = new SolidColorBrush(Color.FromRgb(0xFF, 0x20, 0x20)); + public static readonly SolidColorBrush Green = new(Color.FromRgb(0x20, 0xE0, 0x20)); + public static readonly SolidColorBrush DimGreen = new(Color.FromRgb(0xC0, 0xE0, 0xC0)); + public static readonly SolidColorBrush DimOrange = new(Color.FromRgb(0xFF, 0xC0, 0x80)); + public static readonly SolidColorBrush Yellow = new(Color.FromRgb(0xFF, 0xE0, 0x10)); + public static readonly SolidColorBrush Red = new(Color.FromRgb(0xFF, 0x20, 0x20)); + public static readonly SolidColorBrush DimRed = new(Color.FromRgb(0xFF, 0xC0, 0xC0)); public static Brush ForFillState(FillState state, Color background) { return state switch { diff --git a/PamhagenSysCtrl/Helpers/PamhagenGraph.cs b/PamhagenSysCtrl/Helpers/PamhagenGraph.cs index 857adb2..ec88b03 100644 --- a/PamhagenSysCtrl/Helpers/PamhagenGraph.cs +++ b/PamhagenSysCtrl/Helpers/PamhagenGraph.cs @@ -66,8 +66,8 @@ namespace PamhagenSysCtrl.Helpers { MW1 = new Trough("MW1", mw1X, mpY - 70, () => App.Plant?.FillLevelMW1 ?? FillState.Unknown); MW2 = new Trough("MW2", mw2X, mpY - 70, () => App.Plant?.FillLevelMW2 ?? FillState.Unknown); - MP1 = new Pump("MP1", mw1X, mpY, () => App.Plant?.IsMP1Active ?? false, () => App.Plant?.MP1HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Hops.Any(h => h.Node == x))); - MP2 = new Pump("MP2", mw2X, mpY, () => App.Plant?.IsMP2Active ?? false, () => App.Plant?.MP2HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Hops.Any(h => h.Node == x))); + MP1 = new Pump("MP1", mw1X, mpY, () => App.Plant?.MP1 ?? default, () => App.Plant?.MP1HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Hops.Any(h => h.Node == x))); + MP2 = new Pump("MP2", mw2X, mpY, () => App.Plant?.MP2 ?? default, () => App.Plant?.MP2HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Hops.Any(h => h.Node == x))); var x1 = new PipeJoin(mw1X, mpY + 40); var x2 = new PipeJoin(mw2X, mpY + 40); @@ -291,14 +291,14 @@ namespace PamhagenSysCtrl.Helpers { CreateInlet(v43, t9); var xp12 = new PipeJoin((t1X + t2X) / 2, tanksY + 80); - P12 = new Pump("P1/2", (t1X + t2X) / 2, tanksY + 110, () => App.Plant?.IsP12Active ?? false, () => App.Plant?.P12HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Hops.Any(h => h.Node == x))); + P12 = new Pump("P1/2", (t1X + t2X) / 2, tanksY + 110, () => App.Plant?.P12 ?? default, () => App.Plant?.P12HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Hops.Any(h => h.Node == x))); CreateOutlet(t1, xp12); CreateOutlet(t2, xp12); CreatePipe(xp12, P12); - P3 = new Pump("P3", t3X, tanksY + 110, () => App.Plant?.IsP3Active ?? false, () => App.Plant?.P3HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Hops.Any(h => h.Node == x))); - P4 = new Pump("P4", t4X, tanksY + 110, () => App.Plant?.IsP4Active ?? false, () => App.Plant?.P4HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Hops.Any(h => h.Node == x))); - P5 = new Pump("P5", t5X, tanksY + 110, () => App.Plant?.IsP5Active ?? false, () => App.Plant?.P5HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Hops.Any(h => h.Node == x))); + P3 = new Pump("P3", t3X, tanksY + 110, () => App.Plant?.P3 ?? default, () => App.Plant?.P3HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Hops.Any(h => h.Node == x))); + P4 = new Pump("P4", t4X, tanksY + 110, () => App.Plant?.P4 ?? default, () => App.Plant?.P4HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Hops.Any(h => h.Node == x))); + P5 = new Pump("P5", t5X, tanksY + 110, () => App.Plant?.P5 ?? default, () => App.Plant?.P5HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Hops.Any(h => h.Node == x))); CreateOutlet(t3, P3); CreateOutlet(t4, P4); CreateOutlet(t5, P5); @@ -328,7 +328,7 @@ namespace PamhagenSysCtrl.Helpers { var xt6 = new PipeJoin(t6X, tanksY + 110); var xt7 = new PipeJoin(t7X, tanksY + 110); var xt8 = new PipeJoin(t8X, tanksY + 110); - P6 = new Pump("P6", t6X, tanksY + 150, () => App.Plant?.IsP6Active ?? false, () => App.Plant?.P6HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Hops.Any(h => h.Node == x))); + P6 = new Pump("P6", t6X, tanksY + 150, () => App.Plant?.P6 ?? default, () => App.Plant?.P6HasClearance ?? false, (x) => SelectedPaths.Any(p => p.Hops.Any(h => h.Node == x))); CreateOutlet(t6, xt6); CreateOutlet(t7, xt7); CreateOutlet(t8, xt8); @@ -395,34 +395,35 @@ namespace PamhagenSysCtrl.Helpers { } protected Valve CreateValve(int n, double x, double y) { - return new(n, x, y, () => App.Plant?.IsValveOpen(n) ?? false, (v) => SelectedPaths.Any(p => p.Hops.Any(h => h.Node == v))); + return new(n, x, y, () => App.Plant?.IsValveOpen(n) ?? false, (v) => App.Plant?.ManualControlMode ?? false ? App.Plant.WantValveOpen(n) : SelectedPaths.Any(p => p.Hops.Any(h => h.Node == v))); } protected static Tank CreateTank(int n, double x, double y, int capacity) { return new($"Tank {n}", x, y, capacity, () => App.Plant?.TankFillLevels[n - 1] ?? FillState.Unknown); } - public void TraverseSubgraph(INode start, HashSet? visited = null, Action? edgeAction = null, Action? valveAction = null) { - visited ??= []; + public void TraverseSubgraph(INode start, Func invalid, HashSet? _visited = null, Action? edgeAction = null, Action? valveAction = null, bool reverse = false) { + var visited = _visited ?? []; if (start is Valve v) { valveAction?.Invoke(v); - return; - } else if (start is ISink || start is Pump) { + } + if (invalid(start)) { return; } + foreach (var edge in start.Inputs) { if (visited.Contains(edge)) continue; var o = edge.IsTwoWay && edge.Start == start ? edge.End : edge.Start; - edgeAction?.Invoke(edge); + edgeAction?.Invoke(start, reverse || _visited == null, edge); visited.Add(edge); - TraverseSubgraph(o, visited, edgeAction, valveAction); + TraverseSubgraph(o, invalid, visited, edgeAction, valveAction, reverse || _visited == null); } foreach (var edge in start.Outputs) { if (visited.Contains(edge)) continue; var o = edge.IsTwoWay && edge.End == start ? edge.Start : edge.End; - edgeAction?.Invoke(edge); + edgeAction?.Invoke(start, reverse, edge); visited.Add(edge); - TraverseSubgraph(o, visited, edgeAction, valveAction); + TraverseSubgraph(o, invalid, visited, edgeAction, valveAction, reverse); } } @@ -453,7 +454,7 @@ namespace PamhagenSysCtrl.Helpers { private void UpdateValvesAdjacientToPath(Path path, bool locked, bool? wantOpen = null) { foreach (var (edge, node) in path.Hops) { - TraverseSubgraph(node, valveAction: (v) => { + TraverseSubgraph(node, (n) => n is Valve || n is ISink || n is Pump, valveAction: (v) => { v.IsLocked = locked; if (wantOpen.HasValue) { if (wantOpen.Value) { @@ -466,18 +467,41 @@ namespace PamhagenSysCtrl.Helpers { } } - public void ColorPipesAdjacientToPath(Path path, Brush? color1, bool flowArrows, Brush? color2) { + public void ColorPipesAdjacientToPath(Path path, Brush? color1, MotorState flowArrows, Brush? color2) { var color = color1; var flowPathOffset = 0.0; HashSet visited = []; foreach (var (edge, node) in path.Hops) { edge.Highlight = color1; - edge.Flow = flowArrows ? (edge.End == node, flowPathOffset) : null; - if (edge.Flow != null) flowPathOffset += edge.FlowLength; + if (flowArrows > MotorState.Halt) { + var dir = edge.End == node; + if (flowArrows == MotorState.Backward) dir = !dir; + edge.Flow = (dir, flowPathOffset); + flowPathOffset += edge.FlowLength; + } visited.Add(edge); if (node is Pump) color = color2; - TraverseSubgraph(node, visited, edgeAction: (e) => e.Highlight = color); + TraverseSubgraph(node, (n) => n is Valve || n is ISink || n is Pump, visited, edgeAction: (s, r, e) => e.Highlight = color); } } + + public void ColorPipesByValves(INode start, Brush? color1, MotorState flowArrows, Brush? color2) { + var flowPathOffset = 0.0; + TraverseSubgraph(start, (n) => n is ISink || n is Outlet || !((n as Valve)?.IsOpen ?? (n as CenterValve)?.IsOpen ?? true), null, + edgeAction: (s, r, e) => { + if (((e.Start as Valve)?.IsOpen ?? true) && ((e.End as Valve)?.IsOpen ?? true)) { + e.Highlight = color1; + if (flowArrows == MotorState.Forward || flowArrows == MotorState.Backward) { + var dir = e.Start == s; + if (r) dir = !dir; + if (flowArrows == MotorState.Backward) dir = !dir; + e.Flow = (dir, flowPathOffset); + flowPathOffset += e.FlowLength; + } + } else { + e.Highlight = color2; + } + }); + } } } diff --git a/PamhagenSysCtrl/Helpers/PamhagenPlant.cs b/PamhagenSysCtrl/Helpers/PamhagenPlant.cs index d8d232b..d270f04 100644 --- a/PamhagenSysCtrl/Helpers/PamhagenPlant.cs +++ b/PamhagenSysCtrl/Helpers/PamhagenPlant.cs @@ -9,6 +9,7 @@ namespace PamhagenSysCtrl.Helpers { protected bool IsRunning = true; protected Thread? BackgroundThread; + public bool ManualControlMode { get; set; } public bool OverridePathClearances { get; set; } public bool OverrideDryRunClearances { get; set; } @@ -93,12 +94,12 @@ namespace PamhagenSysCtrl.Helpers { if (p.IsMP1Active && !p.MP1HasClearance) p.StopMP1(); if (p.IsMP2Active && !p.MP2HasClearance) p.StopMP2(); - if (p.IsMP1Active && p.FillLevelMW1 >= FillState.Half && !p.IsStirrerMW1Active) { + if (p.MP1 == MotorState.Forward && p.FillLevelMW1 >= FillState.Half && !p.IsStirrerMW1Active) { p.StartStirrerMW1(); } else if (p.IsStirrerMW1Active && (!p.IsMP1Active || p.FillLevelMW1 < FillState.Half)) { p.StopStirrerMW1(); } - if (p.IsMP2Active && p.FillLevelMW2 >= FillState.Half && !p.IsStirrerMW2Active) { + if (p.MP2 == MotorState.Forward && p.FillLevelMW2 >= FillState.Half && !p.IsStirrerMW2Active) { p.StartStirrerMW2(); } else if (p.IsStirrerMW2Active && (!p.IsMP2Active || p.FillLevelMW2 < FillState.Half)) { p.StopStirrerMW2(); diff --git a/PamhagenSysCtrl/Helpers/Pipeline/Pump.cs b/PamhagenSysCtrl/Helpers/Pipeline/Pump.cs index 5e13eae..28316df 100644 --- a/PamhagenSysCtrl/Helpers/Pipeline/Pump.cs +++ b/PamhagenSysCtrl/Helpers/Pipeline/Pump.cs @@ -16,11 +16,12 @@ namespace PamhagenSysCtrl.Helpers.Pipeline { public ISet Outputs { get; init; } public Brush? Highlight { get; set; } - public bool IsActive => CallbackActive(); + public bool IsActive => State == MotorState.Forward || State == MotorState.Backward; + public MotorState State => CallbackState(); public bool HasClearance => CallbackClearance(); public bool IsSelected => CallbackSelected(this); - protected Func CallbackActive; + protected Func CallbackState; protected Func CallbackClearance; protected Func CallbackSelected; @@ -28,11 +29,11 @@ namespace PamhagenSysCtrl.Helpers.Pipeline { private Shape? _triangle; private TextBlock? _text; - public Pump(string label, double x, double y, Func cbActive, Func cbClearance, Func cbSelected) { + public Pump(string label, double x, double y, Func cbState, Func cbClearance, Func cbSelected) { CenterX = x; CenterY = y; Label = label; - CallbackActive = cbActive; + CallbackState = cbState; CallbackClearance = cbClearance; CallbackSelected = cbSelected; Inputs = new HashSet(); @@ -75,13 +76,22 @@ namespace PamhagenSysCtrl.Helpers.Pipeline { } public void Update(bool isHovering, bool isPresed) { - _circle?.Fill = Highlight ?? ( - IsActive && isHovering ? PamhagenBrushes.Red : - IsActive ? PamhagenBrushes.Green : - HasClearance && IsSelected && isHovering ? PamhagenBrushes.Green : - HasClearance && IsSelected ? PamhagenBrushes.Yellow : - IsSelected ? PamhagenBrushes.Red : - isHovering ? Brushes.LightGray : Brushes.WhiteSmoke); + if (App.Plant?.ManualControlMode ?? false) { + _circle?.Fill = Highlight ?? ( + IsActive && isHovering ? PamhagenBrushes.Red : + IsActive ? PamhagenBrushes.Green : + HasClearance && isHovering ? PamhagenBrushes.Green : + HasClearance ? PamhagenBrushes.Yellow : + PamhagenBrushes.Red); + } else { + _circle?.Fill = Highlight ?? ( + IsActive && isHovering ? PamhagenBrushes.Red : + IsActive ? PamhagenBrushes.Green : + HasClearance && IsSelected && isHovering ? PamhagenBrushes.Green : + HasClearance && IsSelected ? PamhagenBrushes.Yellow : + IsSelected ? PamhagenBrushes.Red : + isHovering ? Brushes.LightGray : Brushes.WhiteSmoke); + } } } } diff --git a/PamhagenSysCtrl/Helpers/Pipeline/Valve.cs b/PamhagenSysCtrl/Helpers/Pipeline/Valve.cs index f81aeae..85d56b3 100644 --- a/PamhagenSysCtrl/Helpers/Pipeline/Valve.cs +++ b/PamhagenSysCtrl/Helpers/Pipeline/Valve.cs @@ -66,12 +66,23 @@ namespace PamhagenSysCtrl.Helpers.Pipeline { } public void Update(bool isHovering, bool isPressed) { - _circle?.Fill = - IsLocked && !WantOpen && !IsOpen ? PamhagenBrushes.DimOrange : - IsLocked && !WantOpen && IsOpen ? PamhagenBrushes.Red : - IsLocked && WantOpen && !IsOpen ? PamhagenBrushes.Yellow : - IsLocked && WantOpen && IsOpen ? PamhagenBrushes.Green : - Highlight ?? (isHovering ? Brushes.LightGray : Brushes.DarkGray); + if (App.Plant?.ManualControlMode ?? false) { + _circle?.Fill = + !WantOpen && !IsOpen && !isHovering ? PamhagenBrushes.Red : + !WantOpen && !IsOpen ? PamhagenBrushes.Green : + !WantOpen && IsOpen ? PamhagenBrushes.DimRed : + WantOpen && !IsOpen ? PamhagenBrushes.DimGreen : + WantOpen && IsOpen && !isHovering ? PamhagenBrushes.Green : + WantOpen && IsOpen ? PamhagenBrushes.Red : + Highlight ?? (isHovering ? Brushes.LightGray : Brushes.DarkGray); + } else { + _circle?.Fill = + IsLocked && !WantOpen && !IsOpen ? PamhagenBrushes.DimOrange : + IsLocked && !WantOpen && IsOpen ? PamhagenBrushes.Red : + IsLocked && WantOpen && !IsOpen ? PamhagenBrushes.Yellow : + IsLocked && WantOpen && IsOpen ? PamhagenBrushes.Green : + Highlight ?? (isHovering ? Brushes.LightGray : Brushes.DarkGray); + } } } } diff --git a/PamhagenSysCtrl/Windows/PlantSchemeWindow.xaml.cs b/PamhagenSysCtrl/Windows/PlantSchemeWindow.xaml.cs index 3bde5d7..2e178bc 100644 --- a/PamhagenSysCtrl/Windows/PlantSchemeWindow.xaml.cs +++ b/PamhagenSysCtrl/Windows/PlantSchemeWindow.xaml.cs @@ -22,6 +22,7 @@ namespace PamhagenSysCtrl.Windows { } private void Menu_Settings_ManualControl_Click(object sender, RoutedEventArgs evt) { + App.Plant?.ManualControlMode = Menu_Settings_ManualControl.IsChecked; if (Menu_Settings_ManualControl.IsChecked) { foreach (var win in Application.Current.Windows) { if (win is ManualControlWindow w) { @@ -38,6 +39,7 @@ namespace PamhagenSysCtrl.Windows { } } } + OnUpdate(null, null); } private void Menu_Settings_OverridePathClearances_Click(object sender, RoutedEventArgs evt) { @@ -85,27 +87,37 @@ namespace PamhagenSysCtrl.Windows { _lastSource = null; } foreach (var c in clicked) { - if (_lastSource == null && Graph.SelectedPaths.Any(p => p.Start == c)) { - var path = Graph.SelectedPaths.First(p => p.Start == c); - if (!path.Hops.Any(h => (h.Node as Pump)?.IsActive ?? false)) { - Graph.RemovePath(path); + if (App.Plant?.ManualControlMode ?? false) { + if (c is Valve v) { + if (v.WantOpen) { + App.Plant.CloseV(v.Nr); + } else { + App.Plant.OpenV(v.Nr); + } } - } else if (_lastSource != null && c is ISink sink) { - var res = Graph.GetPath([_lastSource, .._path, sink], (n) => n is not ISink && (n is not Valve v || !v.IsLocked)); - if (res is Path path) { - Graph.AddPath(path); + } else { + if (_lastSource == null && Graph.SelectedPaths.Any(p => p.Start == c)) { + var path = Graph.SelectedPaths.First(p => p.Start == c); + if (!path.Hops.Any(h => (h.Node as Pump)?.IsActive ?? false)) { + Graph.RemovePath(path); + } + } else if (_lastSource != null && c is ISink sink) { + var res = Graph.GetPath([_lastSource, .. _path, sink], (n) => n is not ISink && (n is not Valve v || !v.IsLocked)); + if (res is Path path) { + Graph.AddPath(path); + _lastSource = null; + } + } else if (_lastSource != null) { + var path = Graph.GetPath([_lastSource, .. _path, c], (n) => n is not ISink && (n is not Valve v || !v.IsLocked)); + if (path != null) { + _path.Add(c); + } + } else if (c is ISource source) { + _lastSource = source; + _path = []; + } else { _lastSource = null; } - } else if (_lastSource != null) { - var path = Graph.GetPath([_lastSource, .. _path, c], (n) => n is not ISink && (n is not Valve v || !v.IsLocked)); - if (path != null) { - _path.Add(c); - } - } else if (c is ISource source) { - _lastSource = source; - _path = []; - } else { - _lastSource = null; } try { if (c is EntryTrough trough) { @@ -208,35 +220,42 @@ namespace PamhagenSysCtrl.Windows { p.Highlight = null; } } - foreach (var p in Graph.SelectedPaths) { - Graph.ColorPipesAdjacientToPath(p, PamhagenBrushes.Green, p.Hops.Any(h => (h.Node as Pump)?.IsActive ?? false), PamhagenBrushes.DimOrange); - } + _hover = Graph.GetHover(pos.X, pos.Y); - if (_lastSource != null && (_hover.Count > 0 || _path.Count > 0)) { - List points = [_lastSource, .. _path]; - if (_hover.Count > 0 && !points.Contains(_hover.First())) { - points.Add(_hover.First()); + if (App.Plant?.ManualControlMode ?? false) { + foreach (var src in Graph.Nodes.Where(n => n is Pump)) { + Graph.ColorPipesByValves(src, PamhagenBrushes.Green, (src as Pump)?.State ?? default, PamhagenBrushes.DimOrange); } - if (points.Count >= 2) { - var res = Graph.GetPath(points, (n) => n is not ISink && (n is not Valve v || !v.IsLocked)); - if (res is Path path) { - Graph.ColorPipesAdjacientToPath(path, PamhagenBrushes.Green, false, PamhagenBrushes.DimOrange); + } else { + foreach (var p in Graph.SelectedPaths) { + Graph.ColorPipesAdjacientToPath(p, PamhagenBrushes.Green, p.Hops.Select(h => (h.Node as Pump)?.State ?? default).FirstOrDefault(n => n != MotorState.Halt), PamhagenBrushes.DimOrange); + } + if (_lastSource != null && (_hover.Count > 0 || _path.Count > 0)) { + List points = [_lastSource, .. _path]; + if (_hover.Count > 0 && !points.Contains(_hover.First())) { + points.Add(_hover.First()); + } + if (points.Count >= 2) { + var res = Graph.GetPath(points, (n) => n is not ISink && (n is not Valve v || !v.IsLocked)); + if (res is Path path) { + Graph.ColorPipesAdjacientToPath(path, PamhagenBrushes.Green, default, PamhagenBrushes.DimOrange); + } + } + } else if (_lastSource == null && _hover.Count > 0 && Graph.SelectedPaths.Any(p => p.Start == _hover.First())) { + var path = Graph.SelectedPaths.First(p => p.Start == _hover.First()); + var pumpActive = path.Hops.Any(h => (h.Node as Pump)?.IsActive ?? false); + if (pumpActive) { + foreach (var pump in path.Hops.Select(h => h.Node as Pump).Where(p => p != null)) { + pump?.Highlight = PamhagenBrushes.Red; + } + } else { + Graph.ColorPipesAdjacientToPath(path, PamhagenBrushes.Red, default, null); } } - } else if (_lastSource == null && _hover.Count > 0 && Graph.SelectedPaths.Any(p => p.Start == _hover.First())) { - var path = Graph.SelectedPaths.First(p => p.Start == _hover.First()); - var pumpActive = path.Hops.Any(h => (h.Node as Pump)?.IsActive ?? false); - if (pumpActive) { - foreach (var pump in path.Hops.Select(h => h.Node as Pump).Where(p => p != null)) { - pump?.Highlight = PamhagenBrushes.Red; + if (_lastSource != null) { + foreach (var edge in _lastSource.Outputs) { + edge.Highlight = PamhagenBrushes.Green; } - } else { - Graph.ColorPipesAdjacientToPath(path, PamhagenBrushes.Red, false, null); - } - } - if (_lastSource != null) { - foreach (var edge in _lastSource.Outputs) { - edge.Highlight = PamhagenBrushes.Green; } } Graph.Update(_hover, down);