diff --git a/PamhagenSysCtrl/Helpers/Pipeline/Graph.cs b/PamhagenSysCtrl/Helpers/Pipeline/Graph.cs index ebd067a..8ba99ee 100644 --- a/PamhagenSysCtrl/Helpers/Pipeline/Graph.cs +++ b/PamhagenSysCtrl/Helpers/Pipeline/Graph.cs @@ -1,10 +1,12 @@ using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Shapes; namespace PamhagenSysCtrl.Helpers.Pipeline { public class Graph { - protected HashSet Nodes = []; - protected HashSet Edges = []; + public HashSet Nodes = []; + public HashSet Edges = []; public Graph() { @@ -20,7 +22,32 @@ namespace PamhagenSysCtrl.Helpers.Pipeline { return p; } + public Path? GetPath(INode start, INode end, Func? valid = null) { + if (!Nodes.Contains(start) || !Nodes.Contains(end)) throw new ArgumentException("Start/end node not contained in graph"); + valid ??= a => true; + List<(IEdge, INode)> hops = []; + Path? path = null; + foreach (var o in start.Outputs) { + if (!valid(o.End)) { + continue; + } else if (o.End == end) { + return new(start, [(o, o.End)]); + } + + var p = GetPath(o.End, end, valid); + if (p != null && (path == null || p.Value.Hops.Count() + 1 < path.Value.Hops.Count())) { + path = new(start, [(o, o.End), ..p.Value.Hops]); + } + } + return path != null ? new(start, path.Value.Hops) : null; + } + public void Draw(Canvas canvas) { + canvas.Children.Add(new Rectangle() { + Fill = Brushes.White, + Width = 5000, + Height = 5000, + }); foreach (var e in Edges) { e.Draw(canvas); } @@ -29,13 +56,17 @@ namespace PamhagenSysCtrl.Helpers.Pipeline { } } - public void Update(double x, double y) { + public ISet Update(double x, double y) { + HashSet hover = new(); foreach (var e in Edges) { e.Update(x, y); } foreach (var n in Nodes) { - n.Update(x, y); + if (n.Update(x, y)) { + hover.Add(n); + } } + return hover; } } } diff --git a/PamhagenSysCtrl/Helpers/Pipeline/IEdge.cs b/PamhagenSysCtrl/Helpers/Pipeline/IEdge.cs index 7267fc1..e80b710 100644 --- a/PamhagenSysCtrl/Helpers/Pipeline/IEdge.cs +++ b/PamhagenSysCtrl/Helpers/Pipeline/IEdge.cs @@ -6,6 +6,7 @@ namespace PamhagenSysCtrl.Helpers.Pipeline { public INode Start { get; } public INode End { get; } public bool Orientation { get; } + public bool Highlight { get; set; } public void Draw(Canvas canvas); public bool Update(double x, double y); diff --git a/PamhagenSysCtrl/Helpers/Pipeline/Path.cs b/PamhagenSysCtrl/Helpers/Pipeline/Path.cs new file mode 100644 index 0000000..cea4efa --- /dev/null +++ b/PamhagenSysCtrl/Helpers/Pipeline/Path.cs @@ -0,0 +1,3 @@ +namespace PamhagenSysCtrl.Helpers.Pipeline { + public record struct Path(INode Start, IEnumerable<(IEdge Edge, INode Node)> Hops); +} diff --git a/PamhagenSysCtrl/Helpers/Pipeline/Pipe.cs b/PamhagenSysCtrl/Helpers/Pipeline/Pipe.cs index c816347..0550c8a 100644 --- a/PamhagenSysCtrl/Helpers/Pipeline/Pipe.cs +++ b/PamhagenSysCtrl/Helpers/Pipeline/Pipe.cs @@ -9,6 +9,12 @@ namespace PamhagenSysCtrl.Helpers.Pipeline { public INode Start { get; set; } public INode End { get; set; } public bool Orientation { get; set; } + public bool Highlight { get; set; } + + private Line? _outer1; + private Line? _outer2; + private Line? _inner1; + private Line? _inner2; public Pipe(INode start, INode end, bool orientation = false) { Start = start; @@ -23,41 +29,43 @@ namespace PamhagenSysCtrl.Helpers.Pipeline { var y2 = Orientation ? End.CenterY : Start.CenterY; var x3 = End.CenterX; var y3 = End.CenterY; - var a1 = new Line() { + _outer1 = new Line() { X1 = x1, Y1 = y1, X2 = x2, Y2 = y2, StrokeThickness = 10, Stroke = Brushes.Black, StrokeStartLineCap = PenLineCap.Square, StrokeEndLineCap = PenLineCap.Square, }; - var a2 = new Line() { + _inner1 = new Line() { X1 = x1, Y1 = y1, X2 = x2, Y2 = y2, StrokeThickness = 6, - Stroke = Brushes.Gray, + Stroke = Brushes.DarkGray, StrokeStartLineCap = PenLineCap.Square, StrokeEndLineCap = PenLineCap.Square, }; - var b1 = new Line() { + _outer2 = new Line() { X1 = x2, Y1 = y2, X2 = x3, Y2 = y3, StrokeThickness = 10, Stroke = Brushes.Black, StrokeStartLineCap = PenLineCap.Square, StrokeEndLineCap = PenLineCap.Square, }; - var b2 = new Line() { + _inner2 = new Line() { X1 = x2, Y1 = y2, X2 = x3, Y2 = y3, StrokeThickness = 6, - Stroke = Brushes.Gray, + Stroke = Brushes.DarkGray, StrokeStartLineCap = PenLineCap.Square, StrokeEndLineCap = PenLineCap.Square, }; - canvas.Children.Add(a1); - canvas.Children.Add(b1); - canvas.Children.Add(a2); - canvas.Children.Add(b2); + canvas.Children.Add(_outer1); + canvas.Children.Add(_outer2); + canvas.Children.Add(_inner1); + canvas.Children.Add(_inner2); } public bool Update(double x, double y) { + _inner1?.Stroke = Highlight ? Brushes.Green: Brushes.DarkGray; + _inner2?.Stroke = Highlight ? Brushes.Green : Brushes.DarkGray; return false; } } diff --git a/PamhagenSysCtrl/Helpers/Pipeline/PipeJoin.cs b/PamhagenSysCtrl/Helpers/Pipeline/PipeJoin.cs index 3d4b5e2..1427d96 100644 --- a/PamhagenSysCtrl/Helpers/Pipeline/PipeJoin.cs +++ b/PamhagenSysCtrl/Helpers/Pipeline/PipeJoin.cs @@ -1,18 +1,23 @@ -using System.Windows.Controls; +using System.Windows; +using System.Windows.Controls; using System.Windows.Media; using System.Windows.Shapes; namespace PamhagenSysCtrl.Helpers.Pipeline { public class PipeJoin : INode { + public const double SIZE = 18; + public const double POINT = 4; + public double CenterX { get; set; } public double CenterY { get; set; } public string Label { get; } = ""; public ISet Inputs { get; init; } - public ISet Outputs { get; init; } + private Polygon? _rect; + public PipeJoin(double x, double y) { CenterX = x; CenterY = y; @@ -21,18 +26,53 @@ namespace PamhagenSysCtrl.Helpers.Pipeline { } public void Draw(Canvas canvas) { - var e = new Rectangle() { - Height = 18, - Width = 18, - Fill = Brushes.Black, + bool? top = Inputs.Any(e => e.Start.CenterY < CenterY && (!e.Orientation || e.Start.CenterX == CenterX)) ? false : Outputs.Any(e => e.End.CenterY < CenterY && (e.Orientation || e.Start.CenterX == CenterX)) ? true : null; + bool? right = Inputs.Any(e => e.Start.CenterX > CenterX && (e.Orientation || e.Start.CenterY == CenterY)) ? false : Outputs.Any(e => e.End.CenterX > CenterX && (!e.Orientation || e.Start.CenterY == CenterY)) ? true : null; + bool? bottom = Inputs.Any(e => e.Start.CenterY > CenterY && (!e.Orientation || e.Start.CenterX == CenterX)) ? false : Outputs.Any(e => e.End.CenterY > CenterY && (e.Orientation || e.Start.CenterX == CenterX)) ? true : null; + bool? left = Inputs.Any(e => e.Start.CenterX < CenterX && (e.Orientation || e.Start.CenterY == CenterY)) ? false : Outputs.Any(e => e.End.CenterX < CenterX && (!e.Orientation || e.Start.CenterY == CenterY)) ? true : null; + + var points = new List { + new(0, 0) }; - e.SetValue(Canvas.LeftProperty, CenterX - 9); - e.SetValue(Canvas.TopProperty, CenterY - 9); - canvas.Children.Add(e); + if (top == true) { + points.AddRange([new(SIZE / 2 - POINT, 0), new(SIZE / 2, -POINT), new(SIZE / 2 + POINT, 0)]); + } else if (top == false) { + points.AddRange([new(SIZE / 2 - POINT, 0), new(SIZE / 2, POINT), new(SIZE / 2 + POINT, 0)]); + } + points.Add(new(SIZE, 0)); + if (right == true) { + points.AddRange([new(SIZE, SIZE / 2 - POINT), new(SIZE + POINT, SIZE / 2), new(SIZE, SIZE / 2 + POINT)]); + } else if (right == false) { + points.AddRange([new(SIZE, SIZE / 2 - POINT), new(SIZE - POINT, SIZE / 2), new(SIZE, SIZE / 2 + POINT)]); + } + points.Add(new(SIZE, SIZE)); + if (bottom == true) { + points.AddRange([new(SIZE / 2 + POINT, SIZE), new(SIZE / 2, SIZE + POINT), new(SIZE / 2 - POINT, SIZE)]); + } else if (bottom == false) { + points.AddRange([new(SIZE / 2 + POINT, SIZE), new(SIZE / 2, SIZE - POINT), new(SIZE / 2 - POINT, SIZE)]); + } + points.Add(new(0, SIZE)); + if (left == true) { + points.AddRange([new(0, SIZE / 2 + POINT), new(-POINT, SIZE / 2), new(0, SIZE / 2 - POINT)]); + } else if (left == false) { + points.AddRange([new(0, SIZE / 2 + POINT), new(POINT, SIZE / 2), new(0, SIZE / 2 - POINT)]); + } + + _rect = new Polygon() { + Points = [..points], + Fill = Brushes.Black, + Stroke = Brushes.Black, + StrokeThickness = 2, + }; + _rect.SetValue(Canvas.LeftProperty, CenterX - SIZE / 2); + _rect.SetValue(Canvas.TopProperty, CenterY - SIZE / 2); + canvas.Children.Add(_rect); } public bool Update(double x, double y) { - return false; + var hover = Math.Abs(x - CenterX) <= SIZE / 2 && Math.Abs(y - CenterY) <= SIZE / 2; + _rect?.Stroke = hover ? Brushes.Black : Brushes.Black; + return hover; } } } diff --git a/PamhagenSysCtrl/Helpers/Pipeline/Pump.cs b/PamhagenSysCtrl/Helpers/Pipeline/Pump.cs index b43a61a..f9d95d1 100644 --- a/PamhagenSysCtrl/Helpers/Pipeline/Pump.cs +++ b/PamhagenSysCtrl/Helpers/Pipeline/Pump.cs @@ -1,16 +1,22 @@ using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Shapes; namespace PamhagenSysCtrl.Helpers.Pipeline { public class Pump : INode { + public const double RADIUS = 24; + 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 Ellipse? _circle; + private TextBlock? _text; + public Pump(string label, double x, double y) { CenterX = x; CenterY = y; @@ -20,7 +26,16 @@ namespace PamhagenSysCtrl.Helpers.Pipeline { } public void Draw(Canvas canvas) { - + _circle = new() { + Height = RADIUS, + Width = RADIUS, + Fill = Brushes.WhiteSmoke, + Stroke = Brushes.Black, + StrokeThickness = 2, + }; + _circle.SetValue(Canvas.LeftProperty, CenterX - RADIUS / 2); + _circle.SetValue(Canvas.TopProperty, CenterY - RADIUS / 2); + canvas.Children.Add(_circle); } public bool Update(double x, double y) { diff --git a/PamhagenSysCtrl/Helpers/Pipeline/Valve.cs b/PamhagenSysCtrl/Helpers/Pipeline/Valve.cs index 481b86b..429b982 100644 --- a/PamhagenSysCtrl/Helpers/Pipeline/Valve.cs +++ b/PamhagenSysCtrl/Helpers/Pipeline/Valve.cs @@ -1,10 +1,13 @@ -using System.Windows.Controls; +using System.Windows; +using System.Windows.Controls; using System.Windows.Media; using System.Windows.Shapes; namespace PamhagenSysCtrl.Helpers.Pipeline { public class Valve : INode { + public const double RADIUS = 24; + public double CenterX { get; set; } public double CenterY { get; set; } public string Label { get; set; } @@ -17,7 +20,8 @@ namespace PamhagenSysCtrl.Helpers.Pipeline { protected Func CallbackIsOpen; protected Func CallbackWantOpen; - private Ellipse Me; + private Ellipse? _circle; + private TextBlock? _text; public Valve(string label, double x, double y, Func cbOpen, Func cbWant) { CenterX = x; @@ -30,21 +34,32 @@ namespace PamhagenSysCtrl.Helpers.Pipeline { } public void Draw(Canvas canvas) { - Me = new Ellipse() { - Height = 20, - Width = 20, - Fill = Brushes.Gray, + _circle = new() { + Height = RADIUS, + Width = RADIUS, + Fill = Brushes.DarkGray, Stroke = Brushes.Black, StrokeThickness = 2, }; - Me.SetValue(Canvas.LeftProperty, CenterX - 10); - Me.SetValue(Canvas.TopProperty, CenterY - 10); - canvas.Children.Add(Me); + _circle.SetValue(Canvas.LeftProperty, CenterX - RADIUS / 2); + _circle.SetValue(Canvas.TopProperty, CenterY - RADIUS / 2); + _text = new() { + Text = Label, + FontSize = 10, + Width = RADIUS, + Height = RADIUS, + TextAlignment = TextAlignment.Center, + VerticalAlignment = VerticalAlignment.Center, + }; + _text.SetValue(Canvas.LeftProperty, CenterX - RADIUS / 2); + _text.SetValue(Canvas.TopProperty, CenterY - RADIUS / 2 + 5); + canvas.Children.Add(_circle); + canvas.Children.Add(_text); } public bool Update(double x, double y) { - var hover = Math.Sqrt(Math.Pow(CenterX - x, 2) + Math.Pow(CenterY - y, 2)) <= 10; - Me.Fill = hover ? Brushes.LightGray : Brushes.Gray; + var hover = Math.Sqrt(Math.Pow(CenterX - x, 2) + Math.Pow(CenterY - y, 2)) <= RADIUS / 2; + _circle?.Fill = hover ? Brushes.LightGray : Brushes.DarkGray; return hover; } } diff --git a/PamhagenSysCtrl/Windows/PlantSchemeWindow.xaml.cs b/PamhagenSysCtrl/Windows/PlantSchemeWindow.xaml.cs index 3937a0c..8dbc0f5 100644 --- a/PamhagenSysCtrl/Windows/PlantSchemeWindow.xaml.cs +++ b/PamhagenSysCtrl/Windows/PlantSchemeWindow.xaml.cs @@ -1,16 +1,14 @@ using PamhagenSysCtrl.Helpers; using PamhagenSysCtrl.Helpers.Pipeline; -using System.Drawing; -using System.Security.Cryptography.X509Certificates; using System.Windows; using System.Windows.Input; -using System.Windows.Media; -using System.Windows.Shapes; namespace PamhagenSysCtrl.Windows { public partial class PlantSchemeWindow : Window { private readonly Graph Graph; + private Trough MW1; + private Trough MW2; public PlantSchemeWindow() { InitializeComponent(); @@ -18,15 +16,15 @@ namespace PamhagenSysCtrl.Windows { Graph = new Graph(); - var mw1 = new Trough("MW1", 200, 50, () => App.Plant?.FillLevelA1 != FillState.Empty); - var mw2 = new Trough("MW2", 50, 50, () => App.Plant?.FillLevelA1 != FillState.Empty); + MW1 = new Trough("MW1", 200, 50, () => App.Plant?.FillLevelA1 != FillState.Empty); + MW2 = new Trough("MW2", 50, 50, () => App.Plant?.FillLevelA1 != FillState.Empty); var mp1 = new Pump("MP1", 200, 80); var mp2 = new Pump("MP2", 50, 80); - var x1 = new PipeJoin(200, 100); - var x2 = new PipeJoin(50, 100); + var x1 = new PipeJoin(200, 120); + var x2 = new PipeJoin(50, 120); - Graph.CreatePipe(mw1, mp1); - Graph.CreatePipe(mw2, mp2); + Graph.CreatePipe(MW1, mp1); + Graph.CreatePipe(MW2, mp2); Graph.CreatePipe(mp1, x1); Graph.CreatePipe(mp2, x2); @@ -56,7 +54,7 @@ namespace PamhagenSysCtrl.Windows { var v6 = CreateValve(6, 200, 280); var v7 = CreateValve(7, 125, 240); var v8 = CreateValve(8, 50, 280); - var v9 = CreateValve(9, 300, 240); + var v9 = CreateValve(9, 280, 240); Graph.CreatePipe(x4, v5); Graph.CreatePipe(x4, v6); @@ -64,6 +62,57 @@ namespace PamhagenSysCtrl.Windows { Graph.CreatePipe(x6, v8); Graph.CreatePipe(v7, v9); + var x7 = new PipeJoin(390, 210); + var x8 = new PipeJoin(360, 240); + var x9 = new PipeJoin(320, 240); + + Graph.CreatePipe(v5, x7); + Graph.CreatePipe(v9, x9); + Graph.CreatePipe(x9, x8); + + var v10 = CreateValve(10, 320, 280); + var v11 = CreateValve(11, 360, 180); + var v12 = CreateValve(12, 440, 210); + var v13 = CreateValve(13, 440, 240); + var v14 = CreateValve(14, 490, 180); + var v15 = CreateValve(15, 390, 180); + var v16 = CreateValve(16, 520, 180); + + Graph.CreatePipe(v10, x9); + Graph.CreatePipe(x8, v11); + Graph.CreatePipe(x7, v15); + Graph.CreatePipe(x7, v12); + Graph.CreatePipe(x8, v13); + + var x10 = new PipeJoin(520, 210); + var x11 = new PipeJoin(490, 240); + + Graph.CreatePipe(v12, x10); + Graph.CreatePipe(v13, x11); + Graph.CreatePipe(x10, v16); + Graph.CreatePipe(x11, v14); + + var v20 = CreateValve(20, 650, 180); + var v21 = CreateValve(21, 620, 180); + var x12 = new PipeJoin(650, 210); + var x13 = new PipeJoin(620, 240); + + Graph.CreatePipe(x10, x12); + Graph.CreatePipe(x11, x13); + Graph.CreatePipe(x12, v20); + Graph.CreatePipe(x13, v21); + + var x14 = new PipeJoin(390, 140); + var x15 = new PipeJoin(490, 140); + var x16 = new PipeJoin(620, 140); + + Graph.CreatePipe(v11, x14, true); + Graph.CreatePipe(v15, x14, true); + Graph.CreatePipe(v14, x15, true); + Graph.CreatePipe(v16, x15, true); + Graph.CreatePipe(v20, x16, true); + Graph.CreatePipe(v21, x16, true); + Graph.Draw(SchemeCanvas); } @@ -73,7 +122,19 @@ namespace PamhagenSysCtrl.Windows { private void SchemeCanvas_MouseMove(object sender, MouseEventArgs evt) { var p = evt.GetPosition(SchemeCanvas); - Graph.Update(p.X, p.Y); + foreach (var e in Graph.Edges) { + e.Highlight = false; + } + var hover = Graph.Update(p.X, p.Y); + if (hover.Count > 0) { + var path = Graph.GetPath(MW1, hover.First()); + if (path != null) { + foreach (var (edge, node) in path.Value.Hops) { + edge.Highlight = true; + } + } + Graph.Update(p.X, p.Y); + } } public void OnUpdate(object? sender, PlantEventArgs evt) {