diff --git a/PamhagenSysCtrl/Helpers/PamhagenGraph.cs b/PamhagenSysCtrl/Helpers/PamhagenGraph.cs index 7fd48f4..263b910 100644 --- a/PamhagenSysCtrl/Helpers/PamhagenGraph.cs +++ b/PamhagenSysCtrl/Helpers/PamhagenGraph.cs @@ -84,13 +84,12 @@ namespace PamhagenSysCtrl.Helpers { CreatePipe(x7, xv11); var v10 = CreateValve(10, ltg2X + 40, 360); - var v11 = CreateValve(11, ltg1X - 40, 330); + var v11 = CreateValve(11, ltg1X - 30, 330); var v12 = CreateValve(12, ltg1X, 260); var v13 = CreateValve(13, ltg2X, 260); - var v14 = CreateValve(14, ltg1X - 40, 220); - var v15 = CreateValve(15, ltg1X - 40, 300); - var v16 = CreateValve(16, ltg1X - 40, 190); - + var v14 = CreateValve(14, ltg1X - 30, 220); + var v15 = CreateValve(15, ltg1X - 30, 300); + var v16 = CreateValve(16, ltg1X - 30, 190); CreatePipe(v10, x7); CreatePipe(xv11, v11); CreatePipe(xv15, v15); @@ -99,32 +98,48 @@ namespace PamhagenSysCtrl.Helpers { var xv16 = new PipeJoin(ltg1X, 190); var xv14 = new PipeJoin(ltg2X, 220); - CreatePipe(v12, xv16); CreatePipe(v13, xv14); CreatePipe(v16, xv16); CreatePipe(v14, xv14); - var v20 = CreateValve(20, ltg1X - 40, 60); - var v21 = CreateValve(21, ltg1X - 40, 90); + var v20 = CreateValve(20, ltg1X - 30, 60); + var v21 = CreateValve(21, ltg1X - 30, 90); var xv20 = new PipeJoin(ltg1X, 60); var xv21 = new PipeJoin(ltg2X, 90); - CreatePipe(xv16, xv20); CreatePipe(xv14, xv21); CreatePipe(v20, xv20); CreatePipe(v21, xv21); - var x8 = new PipeJoin(ltg1X - 80, 300); - var x9 = new PipeJoin(ltg1X - 80, 220); - var x10 = new PipeJoin(ltg1X - 80, 90); - - CreatePipe(v11, x8); - CreatePipe(v15, x8); - CreatePipe(x9, v14, true); - CreatePipe(x9, v16, true); - CreatePipe(x10, v20, true); - CreatePipe(x10, v21, true); + var wt1 = new HeatExchanger("WT Most", ltg1X - 180, 260); + var wt2 = new HeatExchanger("WT Warmwasser", ltg1X - 180, 125); + var v17 = CreateValve(17, ltg1X - 110, 260); + var v18 = CreateValve(18, ltg1X - 140, 300); + var v19 = CreateValve(19, ltg1X - 140, 190); + var xwt1 = new PipeJoin(ltg1X - 60, 300); + var xwt2 = new PipeJoin(ltg1X - 110, 300); + var xwt3 = new PipeJoin(ltg1X - 140, 220); + var xwt4 = new PipeJoin(ltg1X - 60, 220); + var xwt5 = new PipeJoin(ltg1X - 140, 160); + var xwt6 = new PipeJoin(ltg1X - 60, 90); + CreatePipe(v11, xwt1); + CreatePipe(v15, xwt1); + CreatePipe(xwt1, xwt2); + CreatePipe(xwt4, v14, true); + CreatePipe(xwt4, v16, true); + CreatePipe(xwt6, v20, true); + CreatePipe(xwt6, v21, true); + CreatePipe(wt2, xwt6, true); + CreatePipe(wt1, xwt3, true); + CreatePipe(xwt3, xwt4); + CreatePipe(xwt2, v18); + CreatePipe(v18, wt1); + CreatePipe(xwt2, v17); + CreatePipe(xwt3, v19); + CreatePipe(v17, xwt5, true); + CreatePipe(v19, xwt5); + CreatePipe(xwt5, wt2); var xv22 = new PipeJoin(t1X - 15, ltg7Y); var xv23 = new PipeJoin(t1X + 15, ltg6Y); @@ -339,7 +354,8 @@ namespace PamhagenSysCtrl.Helpers { return new($"Tank {n}", x, y, capacity, () => App.Plant?.TankFillLevels[n - 1] != FillState.Empty, () => App.Plant?.TankFillLevels[n - 1] != FillState.Full); } - public void ColorSubgraph(INode start, Brush color, Action? valveAction = null) { + public void TraverseSubgraph(INode start, HashSet? visited = null, Action? edgeAction = null, Action? valveAction = null) { + visited ??= []; if (start is Valve v) { valveAction?.Invoke(v); return; @@ -347,16 +363,18 @@ namespace PamhagenSysCtrl.Helpers { return; } foreach (var edge in start.Inputs) { - if (edge.Highlight != null) continue; + if (visited.Contains(edge)) continue; var o = edge.IsTwoWay && edge.Start == start ? edge.End : edge.Start; - edge.Highlight = color; - ColorSubgraph(o, color, valveAction); + edgeAction?.Invoke(edge); + visited.Add(edge); + TraverseSubgraph(o, visited, edgeAction, valveAction); } foreach (var edge in start.Outputs) { - if (edge.Highlight != null) continue; + if (visited.Contains(edge)) continue; var o = edge.IsTwoWay && edge.End == start ? edge.Start : edge.End; - edge.Highlight = color; - ColorSubgraph(o, color, valveAction); + edgeAction?.Invoke(edge); + visited.Add(edge); + TraverseSubgraph(o, visited, edgeAction, valveAction); } } } diff --git a/PamhagenSysCtrl/Helpers/Pipeline/Graph.cs b/PamhagenSysCtrl/Helpers/Pipeline/Graph.cs index be5252e..0d17d41 100644 --- a/PamhagenSysCtrl/Helpers/Pipeline/Graph.cs +++ b/PamhagenSysCtrl/Helpers/Pipeline/Graph.cs @@ -54,11 +54,23 @@ namespace PamhagenSysCtrl.Helpers.Pipeline { return p; } + public Path? GetPath(IEnumerable points, Func? valid = null) { + if (points.Count() < 2) throw new ArgumentException("Path is too short"); + var start = points.First(); + var path = new Path(start, []); + foreach (var end in points.Skip(1)) { + if (GetPath(start, end, valid, visited: [..path.Hops.Select(h => h.Node)]) is not Path p) + return null; + path.Hops = [..path.Hops, ..p.Hops]; + start = end; + } + return path; + } + public Path? GetPath(INode start, INode end, Func? valid = null, HashSet? visited = null) { if (!Nodes.Contains(start) || !Nodes.Contains(end)) throw new ArgumentException("Start/end node not contained in graph"); valid ??= a => true; - visited ??= []; - visited.Add(start); + visited = [.. visited ?? [], start]; List<(IEdge, INode)> hops = []; Path? path = null; foreach (var o in start.Outputs) { diff --git a/PamhagenSysCtrl/Helpers/Pipeline/HeatExchanger.cs b/PamhagenSysCtrl/Helpers/Pipeline/HeatExchanger.cs new file mode 100644 index 0000000..e5dcb34 --- /dev/null +++ b/PamhagenSysCtrl/Helpers/Pipeline/HeatExchanger.cs @@ -0,0 +1,59 @@ +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Shapes; + +namespace PamhagenSysCtrl.Helpers.Pipeline { + public class HeatExchanger : INode { + + public const double WIDTH = 30; + public const double HEIGHT = 120; + + public double CenterX { get; set; } + public double CenterY { get; set; } + public string Label { get; set; } + public ISet Inputs { get; init; } + public ISet Outputs { get; init; } + + private Shape? _rect; + private TextBlock? _text; + + public HeatExchanger(string label, double x, double y) { + CenterX = x; + CenterY = y; + Label = label; + Inputs = new HashSet(); + Outputs = new HashSet(); + } + + public void Draw(Canvas canvas) { + _rect = new Rectangle() { + Width = WIDTH, + Height = HEIGHT, + Stroke = Brushes.Black, + StrokeThickness = 2, + Fill = Brushes.WhiteSmoke, + }; + _text = new() { + Text = Label, + FontSize = 12, + Width = HEIGHT, + TextAlignment = TextAlignment.Center, + VerticalAlignment = VerticalAlignment.Center, + RenderTransform = new RotateTransform(270), + }; + _rect.SetValue(Canvas.LeftProperty, CenterX - WIDTH / 2); + _rect.SetValue(Canvas.TopProperty, CenterY - HEIGHT / 2); + _text.SetValue(Canvas.LeftProperty, CenterX - 9); + _text.SetValue(Canvas.TopProperty, CenterY + HEIGHT / 2); + canvas.Children.Add(_rect); + canvas.Children.Add(_text); + } + + public bool Update(double x, double y, bool down) { + var hover = Math.Abs(x - CenterX) <= WIDTH / 2 && Math.Abs(y - CenterY) <= HEIGHT / 2; + _rect?.Fill = hover ? Brushes.LightGray : Brushes.WhiteSmoke; + return hover; + } + } +} diff --git a/PamhagenSysCtrl/Helpers/Pipeline/ISource.cs b/PamhagenSysCtrl/Helpers/Pipeline/ISource.cs index 2913d6c..5a9da86 100644 --- a/PamhagenSysCtrl/Helpers/Pipeline/ISource.cs +++ b/PamhagenSysCtrl/Helpers/Pipeline/ISource.cs @@ -2,6 +2,5 @@ public interface ISource : INode { public double BottomY { get; } public bool IsFilled { get; } - public bool IsSelected { get; set; } } } diff --git a/PamhagenSysCtrl/Helpers/Pipeline/Path.cs b/PamhagenSysCtrl/Helpers/Pipeline/Path.cs index 8a5408e..3a62dec 100644 --- a/PamhagenSysCtrl/Helpers/Pipeline/Path.cs +++ b/PamhagenSysCtrl/Helpers/Pipeline/Path.cs @@ -3,7 +3,7 @@ INode Start, IEnumerable<(IEdge Edge, INode Node)> Hops) { - public readonly double Bends => Hops.Sum(h => h.Edge.Start.CenterX != h.Edge.End.CenterX && h.Edge.Start.CenterY != h.Edge.End.CenterY ? 1 : 0); + public readonly double Bends => Hops.Sum(h => (h.Node is not Valve && h.Node is not PipeJoin ? 1 : 0) + (h.Edge.Start.CenterX != h.Edge.End.CenterX && h.Edge.Start.CenterY != h.Edge.End.CenterY ? 1 : 0)); public readonly double Length => Hops.Sum(h => Math.Abs(h.Edge.Start.CenterX - h.Edge.End.CenterX) + Math.Abs(h.Edge.Start.CenterY - h.Edge.End.CenterY)); } } diff --git a/PamhagenSysCtrl/Helpers/Pipeline/Tank.cs b/PamhagenSysCtrl/Helpers/Pipeline/Tank.cs index d4b6eea..2f5f03d 100644 --- a/PamhagenSysCtrl/Helpers/Pipeline/Tank.cs +++ b/PamhagenSysCtrl/Helpers/Pipeline/Tank.cs @@ -16,7 +16,6 @@ namespace PamhagenSysCtrl.Helpers.Pipeline { public string Label { get; set; } public bool IsFull => CallbackIsFull(); public bool IsFilled => CallbackIsFilled(); - public bool IsSelected { get; set; } public ISet Inputs { get; init; } public ISet Outputs { get; init; } @@ -65,7 +64,7 @@ namespace PamhagenSysCtrl.Helpers.Pipeline { public bool Update(double x, double y, bool down) { var hover = Math.Abs(x - CenterX) <= WIDTH / 2 && Math.Abs(y - CenterY) <= HEIGHT / 2; - _rect?.Fill = IsSelected ? Brushes.LightGray : hover ? Brushes.LightGray : Brushes.WhiteSmoke; + _rect?.Fill = hover ? Brushes.LightGray : Brushes.WhiteSmoke; return hover; } } diff --git a/PamhagenSysCtrl/Helpers/Pipeline/Trough.cs b/PamhagenSysCtrl/Helpers/Pipeline/Trough.cs index e0a9b0b..2e95756 100644 --- a/PamhagenSysCtrl/Helpers/Pipeline/Trough.cs +++ b/PamhagenSysCtrl/Helpers/Pipeline/Trough.cs @@ -14,7 +14,6 @@ namespace PamhagenSysCtrl.Helpers.Pipeline { public double BottomY => CenterY + HEIGHT / 2; public string Label { get; set; } public bool IsFilled => CallbackIsFilled(); - public bool IsSelected { get; set; } public ISet Inputs { get; init; } public ISet Outputs { get; init; } @@ -57,7 +56,7 @@ namespace PamhagenSysCtrl.Helpers.Pipeline { public bool Update(double x, double y, bool down) { var hover = Math.Abs(x - CenterX) <= WIDTH / 2 && Math.Abs(y - CenterY) <= HEIGHT / 2; - _rect?.Fill = IsSelected ? Brushes.LightGray : hover ? Brushes.LightGray : Brushes.WhiteSmoke; + _rect?.Fill = hover ? Brushes.LightGray : Brushes.WhiteSmoke; return hover; } } diff --git a/PamhagenSysCtrl/Helpers/Pipeline/Valve.cs b/PamhagenSysCtrl/Helpers/Pipeline/Valve.cs index bacf8de..a0b91e9 100644 --- a/PamhagenSysCtrl/Helpers/Pipeline/Valve.cs +++ b/PamhagenSysCtrl/Helpers/Pipeline/Valve.cs @@ -61,7 +61,7 @@ namespace PamhagenSysCtrl.Helpers.Pipeline { public bool Update(double x, double y, bool down) { var hover = Math.Sqrt(Math.Pow(CenterX - x, 2) + Math.Pow(CenterY - y, 2)) <= DIAMETER / 2; - _circle?.Fill = IsLocked ? Brushes.Red : Highlight ?? (hover ? Brushes.LightGray : Brushes.DarkGray); + _circle?.Fill = IsLocked ? Brushes.Orange : Highlight ?? (hover ? Brushes.LightGray : Brushes.DarkGray); return hover; } } diff --git a/PamhagenSysCtrl/Windows/PlantSchemeWindow.xaml.cs b/PamhagenSysCtrl/Windows/PlantSchemeWindow.xaml.cs index 7479f7e..d598a29 100644 --- a/PamhagenSysCtrl/Windows/PlantSchemeWindow.xaml.cs +++ b/PamhagenSysCtrl/Windows/PlantSchemeWindow.xaml.cs @@ -11,6 +11,7 @@ namespace PamhagenSysCtrl.Windows { private readonly List Paths = []; private ISource? _lastSource; + private List _path = []; private ISet? _lastSelected; public PlantSchemeWindow() { @@ -31,28 +32,38 @@ namespace PamhagenSysCtrl.Windows { } private void SchemeCanvas_MouseLeftButtonUp(object sender, MouseButtonEventArgs evt) { - var p = evt.GetPosition(SchemeCanvas); - var curSelected = UpdateScheme(evt.GetPosition(SchemeCanvas), evt.LeftButton == MouseButtonState.Pressed); + var pos = evt.GetPosition(SchemeCanvas); + var curSelected = UpdateScheme(pos, evt.LeftButton == MouseButtonState.Pressed); var clicked = curSelected.Intersect(_lastSelected ?? new HashSet()).ToList(); - _lastSource?.IsSelected = false; if (clicked.Count == 0) { _lastSource = null; } foreach (var c in clicked) { - if (_lastSource != null && c is ISink sink) { - var path = Graph.GetPath(_lastSource, sink, (n) => n is not ISink && (n is not Valve v || !v.IsLocked)); - if (path != null) { - Paths.Add(path.Value); + if (_lastSource == null && Paths.Any(p => p.Start == c)) { + var path = Paths.First(p => p.Start == c); + Paths.Remove(path); + UpdateValvesAdjacientToPath(path, false); + foreach (var p in Paths) UpdateValvesAdjacientToPath(p, true); + } 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) { + Paths.Add(path); + UpdateValvesAdjacientToPath(path, true); _lastSource = null; } } else if (c is ISource source) { _lastSource = source; - _lastSource.IsSelected = true; + _path = []; + } 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 { _lastSource = null; } } - UpdateScheme(evt.GetPosition(SchemeCanvas), evt.LeftButton == MouseButtonState.Pressed); + UpdateScheme(pos, evt.LeftButton == MouseButtonState.Pressed); } private ISet UpdateScheme(Point pos, bool down) { @@ -60,26 +71,30 @@ namespace PamhagenSysCtrl.Windows { e.Highlight = null; } foreach (var p in Paths) { - var c = Brushes.Green; - foreach (var (edge, node) in p.Hops) { - edge.Highlight = Brushes.Green; - if (node is Pump) c = Brushes.Orange; - Graph.ColorSubgraph(node, c, (v) => v.IsLocked = true); - } + ColorPipesAdjacientToPath(p, Brushes.Green, Brushes.Orange); } var hover = Graph.Update(pos.X, pos.Y, down); - if (_lastSource != null && hover.Count > 0) { - var path = Graph.GetPath(_lastSource, hover.First(), (n) => n is not ISink && (n is not Valve v || !v.IsLocked)); - if (path != null) { - var c = Brushes.Green; - foreach (var (edge, node) in path.Value.Hops) { - edge.Highlight = Brushes.Green; - if (node is Pump) c = Brushes.Orange; - Graph.ColorSubgraph(node, c); + 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) { + ColorPipesAdjacientToPath(path, Brushes.Green, Brushes.Orange); } } - Graph.Update(pos.X, pos.Y, down); + } else if (_lastSource == null && hover.Count > 0 && Paths.Any(p => p.Start == hover.First())) { + var path = Paths.First(p => p.Start == hover.First()); + ColorPipesAdjacientToPath(path, Brushes.Red, null); } + if (_lastSource != null) { + foreach (var edge in _lastSource.Outputs) { + edge.Highlight = Brushes.Green; + } + } + Graph.Update(pos.X, pos.Y, down); return hover; } @@ -88,5 +103,22 @@ namespace PamhagenSysCtrl.Windows { } + + private void UpdateValvesAdjacientToPath(Path path, bool locked) { + foreach (var (edge, node) in path.Hops) { + Graph.TraverseSubgraph(node, valveAction: (v) => v.IsLocked = locked); + } + } + + private void ColorPipesAdjacientToPath(Path path, Brush? color1, Brush? color2) { + var color = color1; + HashSet visited = []; + foreach (var (edge, node) in path.Hops) { + edge.Highlight = color1; + visited.Add(edge); + if (node is Pump) color = color2; + Graph.TraverseSubgraph(node, visited, edgeAction: (e) => e.Highlight = color); + } + } } }