|
@@ -0,0 +1,395 @@
|
|
|
|
|
+using Avalonia;
|
|
|
|
|
+using Avalonia.Controls;
|
|
|
|
|
+using Avalonia.Media;
|
|
|
|
|
+using Avalonia.Threading;
|
|
|
|
|
+using System;
|
|
|
|
|
+using System.Collections.Generic;
|
|
|
|
|
+using System.Globalization;
|
|
|
|
|
+using YZWater.Core.ViewModels;
|
|
|
|
|
+
|
|
|
|
|
+namespace YZWater.Avalonia.Controls;
|
|
|
|
|
+
|
|
|
|
|
+/// <summary>
|
|
|
|
|
+/// 通用污水提升泵站 2.5D 工艺总览控件。
|
|
|
|
|
+/// </summary>
|
|
|
|
|
+public class PumpStation3DControl : Control
|
|
|
|
|
+{
|
|
|
|
|
+ public static readonly StyledProperty<double> Tank1LevelProperty = AvaloniaProperty.Register<PumpStation3DControl, double>(nameof(Tank1Level));
|
|
|
|
|
+ public static readonly StyledProperty<double> Tank2LevelProperty = AvaloniaProperty.Register<PumpStation3DControl, double>(nameof(Tank2Level));
|
|
|
|
|
+ public static readonly StyledProperty<double> Tank3LevelProperty = AvaloniaProperty.Register<PumpStation3DControl, double>(nameof(Tank3Level));
|
|
|
|
|
+ public static readonly StyledProperty<double> Tank4LevelProperty = AvaloniaProperty.Register<PumpStation3DControl, double>(nameof(Tank4Level));
|
|
|
|
|
+ public static readonly StyledProperty<double> InflowRateProperty = AvaloniaProperty.Register<PumpStation3DControl, double>(nameof(InflowRate));
|
|
|
|
|
+ public static readonly StyledProperty<double> OutflowRateProperty = AvaloniaProperty.Register<PumpStation3DControl, double>(nameof(OutflowRate));
|
|
|
|
|
+ public static readonly StyledProperty<bool> Pump1RunningProperty = AvaloniaProperty.Register<PumpStation3DControl, bool>(nameof(Pump1Running));
|
|
|
|
|
+ public static readonly StyledProperty<bool> Pump2RunningProperty = AvaloniaProperty.Register<PumpStation3DControl, bool>(nameof(Pump2Running));
|
|
|
|
|
+ public static readonly StyledProperty<bool> Pump3RunningProperty = AvaloniaProperty.Register<PumpStation3DControl, bool>(nameof(Pump3Running));
|
|
|
|
|
+ public static readonly StyledProperty<bool> Pump4RunningProperty = AvaloniaProperty.Register<PumpStation3DControl, bool>(nameof(Pump4Running));
|
|
|
|
|
+ public static readonly StyledProperty<bool> Pump5RunningProperty = AvaloniaProperty.Register<PumpStation3DControl, bool>(nameof(Pump5Running));
|
|
|
|
|
+ public static readonly StyledProperty<bool> Fan1RunningProperty = AvaloniaProperty.Register<PumpStation3DControl, bool>(nameof(Fan1Running));
|
|
|
|
|
+ public static readonly StyledProperty<bool> Fan2RunningProperty = AvaloniaProperty.Register<PumpStation3DControl, bool>(nameof(Fan2Running));
|
|
|
|
|
+ public static readonly StyledProperty<ValveStatus> Valve1StatusProperty = AvaloniaProperty.Register<PumpStation3DControl, ValveStatus>(nameof(Valve1Status));
|
|
|
|
|
+ public static readonly StyledProperty<ValveStatus> Valve2StatusProperty = AvaloniaProperty.Register<PumpStation3DControl, ValveStatus>(nameof(Valve2Status));
|
|
|
|
|
+ public static readonly StyledProperty<ValveStatus> Valve3StatusProperty = AvaloniaProperty.Register<PumpStation3DControl, ValveStatus>(nameof(Valve3Status));
|
|
|
|
|
+ public static readonly StyledProperty<ValveStatus> Valve4StatusProperty = AvaloniaProperty.Register<PumpStation3DControl, ValveStatus>(nameof(Valve4Status));
|
|
|
|
|
+ public static readonly StyledProperty<bool> IsInflowRunningProperty = AvaloniaProperty.Register<PumpStation3DControl, bool>(nameof(IsInflowRunning));
|
|
|
|
|
+ public static readonly StyledProperty<bool> IsOutflowRunningProperty = AvaloniaProperty.Register<PumpStation3DControl, bool>(nameof(IsOutflowRunning));
|
|
|
|
|
+ public static readonly StyledProperty<bool> HasAlarmProperty = AvaloniaProperty.Register<PumpStation3DControl, bool>(nameof(HasAlarm));
|
|
|
|
|
+ public static readonly StyledProperty<int> AlarmCountProperty = AvaloniaProperty.Register<PumpStation3DControl, int>(nameof(AlarmCount));
|
|
|
|
|
+ public static readonly StyledProperty<int> RunningDeviceCountProperty = AvaloniaProperty.Register<PumpStation3DControl, int>(nameof(RunningDeviceCount));
|
|
|
|
|
+ public static readonly StyledProperty<double> EfficiencyProperty = AvaloniaProperty.Register<PumpStation3DControl, double>(nameof(Efficiency));
|
|
|
|
|
+
|
|
|
|
|
+ public double Tank1Level { get => GetValue(Tank1LevelProperty); set => SetValue(Tank1LevelProperty, value); }
|
|
|
|
|
+ public double Tank2Level { get => GetValue(Tank2LevelProperty); set => SetValue(Tank2LevelProperty, value); }
|
|
|
|
|
+ public double Tank3Level { get => GetValue(Tank3LevelProperty); set => SetValue(Tank3LevelProperty, value); }
|
|
|
|
|
+ public double Tank4Level { get => GetValue(Tank4LevelProperty); set => SetValue(Tank4LevelProperty, value); }
|
|
|
|
|
+ public double InflowRate { get => GetValue(InflowRateProperty); set => SetValue(InflowRateProperty, value); }
|
|
|
|
|
+ public double OutflowRate { get => GetValue(OutflowRateProperty); set => SetValue(OutflowRateProperty, value); }
|
|
|
|
|
+ public bool Pump1Running { get => GetValue(Pump1RunningProperty); set => SetValue(Pump1RunningProperty, value); }
|
|
|
|
|
+ public bool Pump2Running { get => GetValue(Pump2RunningProperty); set => SetValue(Pump2RunningProperty, value); }
|
|
|
|
|
+ public bool Pump3Running { get => GetValue(Pump3RunningProperty); set => SetValue(Pump3RunningProperty, value); }
|
|
|
|
|
+ public bool Pump4Running { get => GetValue(Pump4RunningProperty); set => SetValue(Pump4RunningProperty, value); }
|
|
|
|
|
+ public bool Pump5Running { get => GetValue(Pump5RunningProperty); set => SetValue(Pump5RunningProperty, value); }
|
|
|
|
|
+ public bool Fan1Running { get => GetValue(Fan1RunningProperty); set => SetValue(Fan1RunningProperty, value); }
|
|
|
|
|
+ public bool Fan2Running { get => GetValue(Fan2RunningProperty); set => SetValue(Fan2RunningProperty, value); }
|
|
|
|
|
+ public ValveStatus Valve1Status { get => GetValue(Valve1StatusProperty); set => SetValue(Valve1StatusProperty, value); }
|
|
|
|
|
+ public ValveStatus Valve2Status { get => GetValue(Valve2StatusProperty); set => SetValue(Valve2StatusProperty, value); }
|
|
|
|
|
+ public ValveStatus Valve3Status { get => GetValue(Valve3StatusProperty); set => SetValue(Valve3StatusProperty, value); }
|
|
|
|
|
+ public ValveStatus Valve4Status { get => GetValue(Valve4StatusProperty); set => SetValue(Valve4StatusProperty, value); }
|
|
|
|
|
+ public bool IsInflowRunning { get => GetValue(IsInflowRunningProperty); set => SetValue(IsInflowRunningProperty, value); }
|
|
|
|
|
+ public bool IsOutflowRunning { get => GetValue(IsOutflowRunningProperty); set => SetValue(IsOutflowRunningProperty, value); }
|
|
|
|
|
+ public bool HasAlarm { get => GetValue(HasAlarmProperty); set => SetValue(HasAlarmProperty, value); }
|
|
|
|
|
+ public int AlarmCount { get => GetValue(AlarmCountProperty); set => SetValue(AlarmCountProperty, value); }
|
|
|
|
|
+ public int RunningDeviceCount { get => GetValue(RunningDeviceCountProperty); set => SetValue(RunningDeviceCountProperty, value); }
|
|
|
|
|
+ public double Efficiency { get => GetValue(EfficiencyProperty); set => SetValue(EfficiencyProperty, value); }
|
|
|
|
|
+
|
|
|
|
|
+ private double _phase;
|
|
|
|
|
+ private IDisposable? _timer;
|
|
|
|
|
+
|
|
|
|
|
+ static PumpStation3DControl()
|
|
|
|
|
+ {
|
|
|
|
|
+ AffectsRender<PumpStation3DControl>(
|
|
|
|
|
+ Tank1LevelProperty, Tank2LevelProperty, Tank3LevelProperty, Tank4LevelProperty,
|
|
|
|
|
+ InflowRateProperty, OutflowRateProperty,
|
|
|
|
|
+ Pump1RunningProperty, Pump2RunningProperty, Pump3RunningProperty, Pump4RunningProperty, Pump5RunningProperty,
|
|
|
|
|
+ Fan1RunningProperty, Fan2RunningProperty,
|
|
|
|
|
+ Valve1StatusProperty, Valve2StatusProperty, Valve3StatusProperty, Valve4StatusProperty,
|
|
|
|
|
+ IsInflowRunningProperty, IsOutflowRunningProperty, HasAlarmProperty, AlarmCountProperty,
|
|
|
|
|
+ RunningDeviceCountProperty, EfficiencyProperty);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
|
|
|
|
+ {
|
|
|
|
|
+ base.OnAttachedToVisualTree(e);
|
|
|
|
|
+ ThemeHelper.ThemeChanged += OnThemeChanged;
|
|
|
|
|
+ _timer = DispatcherTimer.Run(() =>
|
|
|
|
|
+ {
|
|
|
|
|
+ _phase = (_phase + 1) % 120;
|
|
|
|
|
+ InvalidateVisual();
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }, TimeSpan.FromMilliseconds(40));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
|
|
|
|
+ {
|
|
|
|
|
+ ThemeHelper.ThemeChanged -= OnThemeChanged;
|
|
|
|
|
+ _timer?.Dispose();
|
|
|
|
|
+ _timer = null;
|
|
|
|
|
+ base.OnDetachedFromVisualTree(e);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void OnThemeChanged() => InvalidateVisual();
|
|
|
|
|
+
|
|
|
|
|
+ public override void Render(DrawingContext context)
|
|
|
|
|
+ {
|
|
|
|
|
+ base.Render(context);
|
|
|
|
|
+
|
|
|
|
|
+ var bounds = new Rect(Bounds.Size);
|
|
|
|
|
+ if (bounds.Width < 120 || bounds.Height < 120)
|
|
|
|
|
+ return;
|
|
|
|
|
+
|
|
|
|
|
+ context.DrawRectangle(ThemeHelper.PanelBg, null, bounds);
|
|
|
|
|
+
|
|
|
|
|
+ var scene = new Rect(bounds.X + 22, bounds.Y + 16, Math.Max(0, bounds.Width - 44), Math.Max(0, bounds.Height - 32));
|
|
|
|
|
+ var scale = Math.Min(scene.Width / 960.0, scene.Height / 540.0);
|
|
|
|
|
+ var origin = new Point(scene.Center.X - 132 * scale, scene.Y + scene.Height * 0.10);
|
|
|
|
|
+
|
|
|
|
|
+ DrawFloor(context, origin, scale);
|
|
|
|
|
+
|
|
|
|
|
+ // 工艺主线:进水渠道 -> 格栅 -> 集水湿井 -> 潜水泵 -> 阀门井 -> 出水总管。
|
|
|
|
|
+ DrawChannel(context, origin, scale, new Point(40, 188), "进水渠道", Tank1Level);
|
|
|
|
|
+ DrawWetWell(context, origin, scale, new Point(255, 145), Tank2Level);
|
|
|
|
|
+ DrawValveChamber(context, origin, scale, new Point(520, 172), Tank4Level);
|
|
|
|
|
+ DrawCabinet(context, origin, scale, new Point(430, 64));
|
|
|
|
|
+
|
|
|
|
|
+ DrawPipe(context, origin, scale, new Point(32, 244), new Point(258, 244), IsInflowRunning, 20);
|
|
|
|
|
+ DrawPipe(context, origin, scale, new Point(300, 132), new Point(620, 132), IsOutflowRunning || AnyPumpRunning, 122);
|
|
|
|
|
+ DrawPipe(context, origin, scale, new Point(620, 132), new Point(720, 132), IsOutflowRunning, 122);
|
|
|
|
|
+
|
|
|
|
|
+ DrawRiser(context, origin, scale, new Point(322, 228), "P1", Pump1Running);
|
|
|
|
|
+ DrawRiser(context, origin, scale, new Point(382, 228), "P2", Pump2Running);
|
|
|
|
|
+ DrawRiser(context, origin, scale, new Point(442, 228), "P3", Pump3Running);
|
|
|
|
|
+ DrawRiser(context, origin, scale, new Point(352, 292), "P4", Pump4Running);
|
|
|
|
|
+ DrawRiser(context, origin, scale, new Point(412, 292), "P5", Pump5Running);
|
|
|
|
|
+
|
|
|
|
|
+ DrawValve(context, origin, scale, new Point(550, 132), "V1", Valve2Status);
|
|
|
|
|
+ DrawValve(context, origin, scale, new Point(610, 132), "V2", Valve3Status);
|
|
|
|
|
+ DrawValve(context, origin, scale, new Point(670, 132), "V3", Valve4Status);
|
|
|
|
|
+ DrawFlowMeter(context, origin, scale, new Point(116, 218), "进水", InflowRate, IsInflowRunning);
|
|
|
|
|
+ DrawFlowMeter(context, origin, scale, new Point(748, 112), "出水", OutflowRate, IsOutflowRunning);
|
|
|
|
|
+ DrawFan(context, origin, scale, new Point(490, 300), "F1", Fan1Running);
|
|
|
|
|
+ DrawFan(context, origin, scale, new Point(550, 300), "F2", Fan2Running);
|
|
|
|
|
+ DrawHud(context, scene, scale);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private bool AnyPumpRunning => Pump1Running || Pump2Running || Pump3Running || Pump4Running || Pump5Running;
|
|
|
|
|
+
|
|
|
|
|
+ private Point Iso(Point p, double z, Point origin, double scale)
|
|
|
|
|
+ {
|
|
|
|
|
+ return new Point(origin.X + (p.X - p.Y) * 0.78 * scale, origin.Y + (p.X + p.Y) * 0.38 * scale - z * scale);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void DrawFloor(DrawingContext context, Point origin, double scale)
|
|
|
|
|
+ {
|
|
|
|
|
+ var floor = new[] { Iso(new Point(0, 85), 0, origin, scale), Iso(new Point(875, 85), 0, origin, scale), Iso(new Point(875, 430), 0, origin, scale), Iso(new Point(0, 430), 0, origin, scale) };
|
|
|
|
|
+ context.DrawGeometry(new SolidColorBrush(GetColor(ThemeHelper.SurfaceBg), 0.72), new Pen(ThemeHelper.Border, 1), Polygon(floor));
|
|
|
|
|
+
|
|
|
|
|
+ var gridPen = new Pen(new SolidColorBrush(GetColor(ThemeHelper.Border), 0.38), 1);
|
|
|
|
|
+ for (var x = 80; x <= 840; x += 80)
|
|
|
|
|
+ context.DrawLine(gridPen, Iso(new Point(x, 85), 0, origin, scale), Iso(new Point(x, 430), 0, origin, scale));
|
|
|
|
|
+ for (var y = 125; y <= 410; y += 60)
|
|
|
|
|
+ context.DrawLine(gridPen, Iso(new Point(0, y), 0, origin, scale), Iso(new Point(875, y), 0, origin, scale));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void DrawChannel(DrawingContext context, Point origin, double scale, Point p, string name, double level)
|
|
|
|
|
+ {
|
|
|
|
|
+ DrawBox(context, origin, scale, p, 180, 112, 44, StructureColor, ThemeHelper.Border);
|
|
|
|
|
+ DrawBox(context, origin, scale, new Point(p.X + 10, p.Y + 10), 160, 92, Math.Max(8, 34 * Math.Clamp(level, 0, 100) / 100), GetColor(ThemeHelper.Success), null, 0.66);
|
|
|
|
|
+ var gridPen = new Pen(ThemeHelper.TextTertiary, Math.Max(1, scale));
|
|
|
|
|
+ for (var x = p.X + 128; x < p.X + 168; x += 8)
|
|
|
|
|
+ context.DrawLine(gridPen, Iso(new Point(x, p.Y + 20), 46, origin, scale), Iso(new Point(x, p.Y + 92), 46, origin, scale));
|
|
|
|
|
+ DrawText(context, name, Iso(new Point(p.X + 25, p.Y + 14), 62, origin, scale), 12 * scale, ThemeHelper.TextPrimary, FontWeight.Bold);
|
|
|
|
|
+ DrawText(context, "粗格栅", Iso(new Point(p.X + 146, p.Y + 64), 57, origin, scale), 10 * scale, ThemeHelper.TextSecondary, FontWeight.Bold);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void DrawWetWell(DrawingContext context, Point origin, double scale, Point p, double level)
|
|
|
|
|
+ {
|
|
|
|
|
+ const double width = 255;
|
|
|
|
|
+ const double depth = 214;
|
|
|
|
|
+ const double wallHeight = 82;
|
|
|
|
|
+ DrawCutawayWellWalls(context, origin, scale, p, width, depth, wallHeight);
|
|
|
|
|
+ var waterHeight = 14 + 74 * Math.Clamp(level, 0, 100) / 100;
|
|
|
|
|
+ var water = new[]
|
|
|
|
|
+ {
|
|
|
|
|
+ Iso(new Point(p.X + 16, p.Y + 16), waterHeight, origin, scale),
|
|
|
|
|
+ Iso(new Point(p.X + width - 16, p.Y + 16), waterHeight, origin, scale),
|
|
|
|
|
+ Iso(new Point(p.X + width - 16, p.Y + depth - 16), waterHeight, origin, scale),
|
|
|
|
|
+ Iso(new Point(p.X + 16, p.Y + depth - 16), waterHeight, origin, scale)
|
|
|
|
|
+ };
|
|
|
|
|
+ context.DrawGeometry(new SolidColorBrush(GetColor(ThemeHelper.Info), 0.60), new Pen(ThemeHelper.Info, Math.Max(1, scale)), Polygon(water));
|
|
|
|
|
+ var title = Iso(new Point(p.X + 70, p.Y + 14), wallHeight + 26, origin, scale);
|
|
|
|
|
+ DrawText(context, "集水湿井", title, 14 * scale, ThemeHelper.TextPrimary, FontWeight.Bold);
|
|
|
|
|
+ DrawText(context, $"液位 {level:F1}%", title + new Point(0, 15 * scale), 10 * scale, ThemeHelper.Info, FontWeight.Bold);
|
|
|
|
|
+ DrawText(context, "潜水泵组 (3 用 2 备)", Iso(new Point(p.X + 55, p.Y + depth - 18), waterHeight + 6, origin, scale), 10 * scale, ThemeHelper.TextSecondary, FontWeight.Bold);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void DrawCutawayWellWalls(DrawingContext context, Point origin, double scale, Point p, double width, double depth, double height)
|
|
|
|
|
+ {
|
|
|
|
|
+ var wallBrush = new SolidColorBrush(StructureColor, 0.96);
|
|
|
|
|
+ var rearWall = new[]
|
|
|
|
|
+ {
|
|
|
|
|
+ Iso(p, 0, origin, scale),
|
|
|
|
|
+ Iso(new Point(p.X + width, p.Y), 0, origin, scale),
|
|
|
|
|
+ Iso(new Point(p.X + width, p.Y), height, origin, scale),
|
|
|
|
|
+ Iso(p, height, origin, scale)
|
|
|
|
|
+ };
|
|
|
|
|
+ var sideWall = new[]
|
|
|
|
|
+ {
|
|
|
|
|
+ Iso(new Point(p.X + width, p.Y), 0, origin, scale),
|
|
|
|
|
+ Iso(new Point(p.X + width, p.Y + depth), 0, origin, scale),
|
|
|
|
|
+ Iso(new Point(p.X + width, p.Y + depth), height, origin, scale),
|
|
|
|
|
+ Iso(new Point(p.X + width, p.Y), height, origin, scale)
|
|
|
|
|
+ };
|
|
|
|
|
+ context.DrawGeometry(wallBrush, new Pen(ThemeHelper.Border, Math.Max(1, scale)), Polygon(rearWall));
|
|
|
|
|
+ context.DrawGeometry(new SolidColorBrush(Darken(StructureColor, 0.82), 0.98), new Pen(ThemeHelper.Border, Math.Max(1, scale)), Polygon(sideWall));
|
|
|
|
|
+
|
|
|
|
|
+ var rimPen = new Pen(ThemeHelper.TextSecondary, Math.Max(1.2, 1.5 * scale));
|
|
|
|
|
+ context.DrawLine(rimPen, Iso(p, height, origin, scale), Iso(new Point(p.X + width, p.Y), height, origin, scale));
|
|
|
|
|
+ context.DrawLine(rimPen, Iso(new Point(p.X + width, p.Y), height, origin, scale), Iso(new Point(p.X + width, p.Y + depth), height, origin, scale));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void DrawValveChamber(DrawingContext context, Point origin, double scale, Point p, double level)
|
|
|
|
|
+ {
|
|
|
|
|
+ DrawBox(context, origin, scale, p, 168, 145, 58, StructureColor, ThemeHelper.Border);
|
|
|
|
|
+ DrawBox(context, origin, scale, new Point(p.X + 12, p.Y + 12), 144, 121, Math.Max(6, 22 * Math.Clamp(level, 0, 100) / 100), GetColor(ThemeHelper.Success), null, 0.48);
|
|
|
|
|
+ DrawText(context, "阀门井", Iso(new Point(p.X + 55, p.Y + 12), 76, origin, scale), 12 * scale, ThemeHelper.TextPrimary, FontWeight.Bold);
|
|
|
|
|
+ DrawText(context, "止回阀 / 闸阀", Iso(new Point(p.X + 35, p.Y + 124), 67, origin, scale), 10 * scale, ThemeHelper.TextSecondary, FontWeight.Bold);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void DrawCabinet(DrawingContext context, Point origin, double scale, Point p)
|
|
|
|
|
+ {
|
|
|
|
|
+ DrawBox(context, origin, scale, p, 150, 82, 84, StructureColor, ThemeHelper.Border);
|
|
|
|
|
+ DrawText(context, "电控柜 / 变频柜", Iso(new Point(p.X + 20, p.Y + 22), 102, origin, scale), 11 * scale, ThemeHelper.TextPrimary, FontWeight.Bold);
|
|
|
|
|
+ DrawText(context, "干区", Iso(new Point(p.X + 48, p.Y + 70), 95, origin, scale), 10 * scale, ThemeHelper.TextSecondary, FontWeight.Bold);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void DrawRiser(DrawingContext context, Point origin, double scale, Point p, string label, bool running)
|
|
|
|
|
+ {
|
|
|
|
|
+ var basePoint = Iso(p, 20, origin, scale);
|
|
|
|
|
+ var topPoint = Iso(p, 122, origin, scale);
|
|
|
|
|
+ var status = running ? ThemeHelper.Success : ThemeHelper.TextDisabled;
|
|
|
|
|
+ context.DrawLine(new Pen(new SolidColorBrush(Darken(StructureColor, 0.70)), 12 * scale), basePoint, topPoint);
|
|
|
|
|
+ context.DrawLine(new Pen(status, 4 * scale), basePoint, topPoint);
|
|
|
|
|
+ var headerTap = new Point(p.X, 132);
|
|
|
|
|
+ DrawPipe(context, origin, scale, p, headerTap, running, 122);
|
|
|
|
|
+ DrawPump(context, origin, scale, p, label, running);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void DrawTank(DrawingContext context, Point origin, double scale, string name, string code, Point p, double w, double d, double h, double level, Color waterColor)
|
|
|
|
|
+ {
|
|
|
|
|
+ DrawBox(context, origin, scale, p, w, d, h, Color.FromRgb(46, 59, 77), new SolidColorBrush(GetColor(ThemeHelper.Border)));
|
|
|
|
|
+
|
|
|
|
|
+ var waterHeight = h * Math.Clamp(level, 0, 100) / 100.0;
|
|
|
|
|
+ if (waterHeight > 2)
|
|
|
|
|
+ DrawBox(context, origin, scale, new Point(p.X + 8, p.Y + 7), w - 16, d - 14, waterHeight, waterColor, null, 0.62);
|
|
|
|
|
+
|
|
|
|
|
+ var top = Iso(new Point(p.X + w * 0.52, p.Y + d * 0.22), h + 12, origin, scale);
|
|
|
|
|
+ DrawText(context, name, top + new Point(-34 * scale, -30 * scale), 12 * scale, ThemeHelper.TextPrimary, FontWeight.Bold);
|
|
|
|
|
+ DrawText(context, $"{code} {level:F1}%", top + new Point(-34 * scale, -14 * scale), 11 * scale, new SolidColorBrush(waterColor), FontWeight.Bold);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void DrawStructure(DrawingContext context, Point origin, double scale, Point p, double w, double d, double h, string label)
|
|
|
|
|
+ {
|
|
|
|
|
+ DrawBox(context, origin, scale, p, w, d, h, Color.FromRgb(61, 72, 92), new SolidColorBrush(GetColor(ThemeHelper.Border)), 0.95);
|
|
|
|
|
+ var textPoint = Iso(new Point(p.X + w * 0.36, p.Y + d * 0.1), h + 8, origin, scale);
|
|
|
|
|
+ DrawText(context, label, textPoint + new Point(-24 * scale, -20 * scale), 11 * scale, ThemeHelper.TextSecondary, FontWeight.Bold);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void DrawBox(DrawingContext context, Point origin, double scale, Point p, double w, double d, double h, Color color, IBrush? stroke, double opacity = 1)
|
|
|
|
|
+ {
|
|
|
|
|
+ var top = new[] { Iso(p, h, origin, scale), Iso(new Point(p.X + w, p.Y), h, origin, scale), Iso(new Point(p.X + w, p.Y + d), h, origin, scale), Iso(new Point(p.X, p.Y + d), h, origin, scale) };
|
|
|
|
|
+ var left = new[] { Iso(p, 0, origin, scale), Iso(new Point(p.X, p.Y + d), 0, origin, scale), Iso(new Point(p.X, p.Y + d), h, origin, scale), Iso(p, h, origin, scale) };
|
|
|
|
|
+ var right = new[] { Iso(new Point(p.X + w, p.Y), 0, origin, scale), Iso(new Point(p.X + w, p.Y + d), 0, origin, scale), Iso(new Point(p.X + w, p.Y + d), h, origin, scale), Iso(new Point(p.X + w, p.Y), h, origin, scale) };
|
|
|
|
|
+
|
|
|
|
|
+ var pen = stroke == null ? null : new Pen(stroke, 1);
|
|
|
|
|
+ context.DrawGeometry(new SolidColorBrush(Darken(color, 0.50), opacity), pen, Polygon(left));
|
|
|
|
|
+ context.DrawGeometry(new SolidColorBrush(Darken(color, 0.68), opacity), pen, Polygon(right));
|
|
|
|
|
+ context.DrawGeometry(new SolidColorBrush(Lighten(color, 0.20), opacity), pen, Polygon(top));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void DrawPipe(DrawingContext context, Point origin, double scale, Point start, Point end, bool isFlowing, double z)
|
|
|
|
|
+ {
|
|
|
|
|
+ var a = Iso(start, z, origin, scale);
|
|
|
|
|
+ var b = Iso(end, z, origin, scale);
|
|
|
|
|
+ context.DrawLine(new Pen(new SolidColorBrush(Darken(StructureColor, 0.70)), 16 * scale), a, b);
|
|
|
|
|
+ context.DrawLine(new Pen(new SolidColorBrush(Lighten(StructureColor, 0.12)), 5 * scale), a, b);
|
|
|
|
|
+
|
|
|
|
|
+ if (!isFlowing)
|
|
|
|
|
+ return;
|
|
|
|
|
+
|
|
|
|
|
+ var flowBrush = new SolidColorBrush(Color.FromRgb(3, 169, 244));
|
|
|
|
|
+ for (var i = 0; i < 9; i++)
|
|
|
|
|
+ {
|
|
|
|
|
+ var t = ((_phase / 120.0) + i / 9.0) % 1.0;
|
|
|
|
|
+ var p = new Point(a.X + (b.X - a.X) * t, a.Y + (b.Y - a.Y) * t);
|
|
|
|
|
+ context.DrawEllipse(flowBrush, null, new Rect(p.X - 4 * scale, p.Y - 4 * scale, 8 * scale, 8 * scale));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void DrawPump(DrawingContext context, Point origin, double scale, Point p, string label, bool running)
|
|
|
|
|
+ {
|
|
|
|
|
+ var c = Iso(p, 30, origin, scale);
|
|
|
|
|
+ var status = running ? GetColor(ThemeHelper.Success) : GetColor(ThemeHelper.TextDisabled);
|
|
|
|
|
+ var radius = 15 * scale;
|
|
|
|
|
+ var body = new Rect(c.X - radius, c.Y - radius * 1.4, radius * 2, radius * 2.25);
|
|
|
|
|
+ context.DrawRectangle(new SolidColorBrush(Darken(StructureColor, 0.78)), new Pen(new SolidColorBrush(status), Math.Max(1, 1.4 * scale)), body, radius * 0.42);
|
|
|
|
|
+ context.DrawEllipse(new SolidColorBrush(Lighten(StructureColor, 0.12)), new Pen(new SolidColorBrush(status), Math.Max(1, scale)), new Rect(c.X - radius, c.Y - radius * 1.7, radius * 2, radius * 0.78));
|
|
|
|
|
+ context.DrawEllipse(new SolidColorBrush(status), null, new Rect(c.X - 3 * scale, c.Y - radius * 0.55, 6 * scale, 6 * scale));
|
|
|
|
|
+ DrawText(context, label, c + new Point(-9 * scale, radius * 1.3), 10 * scale, ThemeHelper.TextPrimary, FontWeight.Bold);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void DrawFan(DrawingContext context, Point origin, double scale, Point p, string label, bool running)
|
|
|
|
|
+ {
|
|
|
|
|
+ var c = Iso(p, 34, origin, scale);
|
|
|
|
|
+ var r = 24 * scale;
|
|
|
|
|
+ var status = running ? GetColor(ThemeHelper.Info) : GetColor(ThemeHelper.TextDisabled);
|
|
|
|
|
+
|
|
|
|
|
+ context.DrawEllipse(new SolidColorBrush(Color.FromRgb(31, 41, 55)), new Pen(new SolidColorBrush(status), 1.5 * scale), new Rect(c.X - r, c.Y - r, r * 2, r * 2));
|
|
|
|
|
+ for (var i = 0; i < 5; i++)
|
|
|
|
|
+ {
|
|
|
|
|
+ var a = (_phase * 7 + i * 72) * Math.PI / 180.0;
|
|
|
|
|
+ context.DrawLine(new Pen(new SolidColorBrush(status, running ? 0.9 : 0.45), 5 * scale), c, new Point(c.X + Math.Cos(a) * r * 0.7, c.Y + Math.Sin(a) * r * 0.7));
|
|
|
|
|
+ }
|
|
|
|
|
+ context.DrawEllipse(new SolidColorBrush(status), null, new Rect(c.X - 5 * scale, c.Y - 5 * scale, 10 * scale, 10 * scale));
|
|
|
|
|
+ DrawText(context, label, c + new Point(-10 * scale, r + 5 * scale), 11 * scale, ThemeHelper.TextPrimary, FontWeight.Bold);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void DrawValve(DrawingContext context, Point origin, double scale, Point p, string label, ValveStatus status)
|
|
|
|
|
+ {
|
|
|
|
|
+ var c = Iso(p, 42, origin, scale);
|
|
|
|
|
+ var color = status switch
|
|
|
|
|
+ {
|
|
|
|
|
+ ValveStatus.Open => GetColor(ThemeHelper.Success),
|
|
|
|
|
+ ValveStatus.Closed => GetColor(ThemeHelper.Warning),
|
|
|
|
|
+ _ => GetColor(ThemeHelper.TextDisabled)
|
|
|
|
|
+ };
|
|
|
|
|
+ var s = 18 * scale;
|
|
|
|
|
+ var points = new[] { c + new Point(0, -s), c + new Point(s, 0), c + new Point(0, s), c + new Point(-s, 0) };
|
|
|
|
|
+ context.DrawGeometry(new SolidColorBrush(color, 0.85), new Pen(ThemeHelper.Border, 1), Polygon(points));
|
|
|
|
|
+ context.DrawLine(new Pen(ThemeHelper.TextPrimary, 2 * scale), c + new Point(-s * 0.55, 0), c + new Point(s * 0.55, 0));
|
|
|
|
|
+ DrawText(context, label, c + new Point(-10 * scale, s + 3 * scale), 10 * scale, ThemeHelper.TextSecondary, FontWeight.Bold);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void DrawFlowMeter(DrawingContext context, Point origin, double scale, Point p, string label, double value, bool active)
|
|
|
|
|
+ {
|
|
|
|
|
+ var c = Iso(p, 52, origin, scale);
|
|
|
|
|
+ var color = active ? GetColor(ThemeHelper.Success) : GetColor(ThemeHelper.TextDisabled);
|
|
|
|
|
+ var rect = new Rect(c.X - 34 * scale, c.Y - 18 * scale, 68 * scale, 36 * scale);
|
|
|
|
|
+ context.DrawRectangle(new SolidColorBrush(GetColor(ThemeHelper.SurfaceBg), 0.95), new Pen(new SolidColorBrush(color), 1.2 * scale), rect, 4 * scale);
|
|
|
|
|
+ DrawText(context, label, rect.Position + new Point(8 * scale, 3 * scale), 10 * scale, ThemeHelper.TextTertiary, FontWeight.Bold);
|
|
|
|
|
+ DrawText(context, $"{value:F1}", rect.Position + new Point(28 * scale, 16 * scale), 12 * scale, new SolidColorBrush(color), FontWeight.Bold);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void DrawHud(DrawingContext context, Rect scene, double scale)
|
|
|
|
|
+ {
|
|
|
|
|
+ var panel = new Rect(scene.X + 10, scene.Y + 10, 218 * scale, 78 * scale);
|
|
|
|
|
+ context.DrawRectangle(new SolidColorBrush(GetColor(ThemeHelper.SurfaceBg), 0.9), new Pen(ThemeHelper.Border, 1), panel, 4 * scale);
|
|
|
|
|
+ DrawText(context, "通用泵站 3D 总览", panel.Position + new Point(12 * scale, 8 * scale), 13 * scale, ThemeHelper.TextPrimary, FontWeight.Bold);
|
|
|
|
|
+ DrawText(context, "进水渠 -> 集水井 -> 泵组 -> 出水总管", panel.Position + new Point(12 * scale, 30 * scale), 10 * scale, ThemeHelper.TextTertiary, FontWeight.Bold);
|
|
|
|
|
+ DrawText(context, $"RUN {RunningDeviceCount} EFF {Efficiency:F0}% ALM {AlarmCount}", panel.Position + new Point(12 * scale, 50 * scale), 11 * scale, HasAlarm ? ThemeHelper.Danger : ThemeHelper.Success, FontWeight.Bold);
|
|
|
|
|
+ if (HasAlarm)
|
|
|
|
|
+ context.DrawRectangle(null, new Pen(ThemeHelper.Danger, 2), scene, 4);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static StreamGeometry Polygon(IReadOnlyList<Point> points)
|
|
|
|
|
+ {
|
|
|
|
|
+ var geometry = new StreamGeometry();
|
|
|
|
|
+ using var ctx = geometry.Open();
|
|
|
|
|
+ ctx.BeginFigure(points[0], true);
|
|
|
|
|
+ for (var i = 1; i < points.Count; i++)
|
|
|
|
|
+ ctx.LineTo(points[i]);
|
|
|
|
|
+ ctx.EndFigure(true);
|
|
|
|
|
+ return geometry;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static void DrawText(DrawingContext context, string text, Point point, double size, IBrush brush, FontWeight weight)
|
|
|
|
|
+ {
|
|
|
|
|
+ var formatted = new FormattedText(text, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, new Typeface("Microsoft YaHei", FontStyle.Normal, weight), Math.Max(8, size), brush);
|
|
|
|
|
+ context.DrawText(formatted, point);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static Color GetColor(IBrush brush) => brush is SolidColorBrush solid ? solid.Color : Colors.Gray;
|
|
|
|
|
+
|
|
|
|
|
+ private Color StructureColor => Lighten(GetColor(ThemeHelper.Border), 0.18);
|
|
|
|
|
+
|
|
|
|
|
+ private static Color Darken(Color color, double factor) => Color.FromRgb((byte)(color.R * factor), (byte)(color.G * factor), (byte)(color.B * factor));
|
|
|
|
|
+
|
|
|
|
|
+ private static Color Lighten(Color color, double amount)
|
|
|
|
|
+ {
|
|
|
|
|
+ return Color.FromRgb(
|
|
|
|
|
+ (byte)Math.Min(255, color.R + (255 - color.R) * amount),
|
|
|
|
|
+ (byte)Math.Min(255, color.G + (255 - color.G) * amount),
|
|
|
|
|
+ (byte)Math.Min(255, color.B + (255 - color.B) * amount));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ protected override Size MeasureOverride(Size availableSize) => new(760, 420);
|
|
|
|
|
+}
|