|
|
@@ -1,80 +1,94 @@
|
|
|
using Avalonia;
|
|
|
using Avalonia.Controls;
|
|
|
+using Avalonia.Input;
|
|
|
using Avalonia.Media;
|
|
|
using Avalonia.Threading;
|
|
|
using System;
|
|
|
using System.Collections.Generic;
|
|
|
using System.Globalization;
|
|
|
+using System.Linq;
|
|
|
+using System.Windows.Input;
|
|
|
using YZWater.Core.ViewModels;
|
|
|
+using YZWater.Core.Models;
|
|
|
+using YZWater.Avalonia.Controls.Layout;
|
|
|
|
|
|
namespace YZWater.Avalonia.Controls;
|
|
|
|
|
|
/// <summary>
|
|
|
-/// 通用污水提升泵站 2.5D 工艺总览控件。
|
|
|
+/// 通用污水提升泵站 2.5D 工艺总览控件(等轴测投影)。
|
|
|
+/// 根据项目档案 (Profile) + 模拟量/数字量字典自动布局渲染。
|
|
|
/// </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); }
|
|
|
+ // ═══ 动态模式属性 ═══
|
|
|
+
|
|
|
+ public static readonly StyledProperty<ProjectProfile?> ProfileProperty =
|
|
|
+ AvaloniaProperty.Register<PumpStation3DControl, ProjectProfile?>(nameof(Profile));
|
|
|
+
|
|
|
+ public static readonly StyledProperty<IDictionary<string, float>?> AnalogValuesProperty =
|
|
|
+ AvaloniaProperty.Register<PumpStation3DControl, IDictionary<string, float>?>(nameof(AnalogValues));
|
|
|
+
|
|
|
+ public static readonly StyledProperty<IDictionary<string, bool>?> DigitalValuesProperty =
|
|
|
+ AvaloniaProperty.Register<PumpStation3DControl, IDictionary<string, bool>?>(nameof(DigitalValues));
|
|
|
+
|
|
|
+ public static readonly StyledProperty<ICommand?> DeviceClickedCommandProperty =
|
|
|
+ AvaloniaProperty.Register<PumpStation3DControl, ICommand?>(nameof(DeviceClickedCommand));
|
|
|
+
|
|
|
+ public ProjectProfile? Profile
|
|
|
+ {
|
|
|
+ get => GetValue(ProfileProperty);
|
|
|
+ set => SetValue(ProfileProperty, value);
|
|
|
+ }
|
|
|
+
|
|
|
+ public IDictionary<string, float>? AnalogValues
|
|
|
+ {
|
|
|
+ get => GetValue(AnalogValuesProperty);
|
|
|
+ set => SetValue(AnalogValuesProperty, value);
|
|
|
+ }
|
|
|
+
|
|
|
+ public IDictionary<string, bool>? DigitalValues
|
|
|
+ {
|
|
|
+ get => GetValue(DigitalValuesProperty);
|
|
|
+ set => SetValue(DigitalValuesProperty, value);
|
|
|
+ }
|
|
|
+
|
|
|
+ public ICommand? DeviceClickedCommand
|
|
|
+ {
|
|
|
+ get => GetValue(DeviceClickedCommandProperty);
|
|
|
+ set => SetValue(DeviceClickedCommandProperty, value);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ═══ 动画 & 布局 ═══
|
|
|
|
|
|
private double _phase;
|
|
|
+ private double _faultFlash;
|
|
|
private IDisposable? _timer;
|
|
|
+ private ILayoutEngine _layoutEngine = new DefaultLayoutEngine();
|
|
|
+ private LayoutResult? _cachedLayout;
|
|
|
+ private Size _lastCanvasSize;
|
|
|
+
|
|
|
+ private static readonly SolidColorBrush PumpBodyBrush = new(Color.FromRgb(31, 41, 55));
|
|
|
|
|
|
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);
|
|
|
+ AffectsRender<PumpStation3DControl>(ProfileProperty, AnalogValuesProperty, DigitalValuesProperty);
|
|
|
+ }
|
|
|
+
|
|
|
+ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
|
|
+ {
|
|
|
+ base.OnPropertyChanged(change);
|
|
|
+ if (change.Property == ProfileProperty)
|
|
|
+ {
|
|
|
+ if (change.NewValue is ProjectProfile newProfile)
|
|
|
+ _layoutEngine = LayoutEngineFactory.Create(newProfile);
|
|
|
+ _cachedLayout = null;
|
|
|
+ InvalidateVisual();
|
|
|
+ }
|
|
|
+ else if (change.Property == BoundsProperty)
|
|
|
+ {
|
|
|
+ _cachedLayout = null;
|
|
|
+ InvalidateVisual();
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
|
|
@@ -83,8 +97,12 @@ public class PumpStation3DControl : Control
|
|
|
ThemeHelper.ThemeChanged += OnThemeChanged;
|
|
|
_timer = DispatcherTimer.Run(() =>
|
|
|
{
|
|
|
- _phase = (_phase + 1) % 120;
|
|
|
- InvalidateVisual();
|
|
|
+ if (HasDynamicAnimation)
|
|
|
+ {
|
|
|
+ _phase = (_phase + 1) % 120;
|
|
|
+ _faultFlash = (_faultFlash + 0.025) % 1.0;
|
|
|
+ InvalidateVisual();
|
|
|
+ }
|
|
|
return true;
|
|
|
}, TimeSpan.FromMilliseconds(40));
|
|
|
}
|
|
|
@@ -97,171 +115,264 @@ public class PumpStation3DControl : Control
|
|
|
base.OnDetachedFromVisualTree(e);
|
|
|
}
|
|
|
|
|
|
- private void OnThemeChanged() => InvalidateVisual();
|
|
|
+ private void OnThemeChanged() { _cachedLayout = null; InvalidateVisual(); }
|
|
|
+
|
|
|
+ // ═══ 动画判断 ═══
|
|
|
+
|
|
|
+ private bool HasDynamicAnimation =>
|
|
|
+ _cachedLayout?.Devices.Any(d => d.DeviceType == "Pump" && GetDigitalValue($"{d.DeviceId}.Run")) == true;
|
|
|
+
|
|
|
+ // ═══ 值读取 ═══
|
|
|
+
|
|
|
+ private float GetAnalogValue(string key) =>
|
|
|
+ AnalogValues != null && AnalogValues.TryGetValue(key, out var v) ? v : 0f;
|
|
|
+
|
|
|
+ private bool GetDigitalValue(string key) =>
|
|
|
+ DigitalValues != null && DigitalValues.TryGetValue(key, out var v) && v;
|
|
|
+
|
|
|
+ // ═══ 主渲染 ═══
|
|
|
|
|
|
public override void Render(DrawingContext context)
|
|
|
{
|
|
|
base.Render(context);
|
|
|
-
|
|
|
var bounds = new Rect(Bounds.Size);
|
|
|
- if (bounds.Width < 120 || bounds.Height < 120)
|
|
|
- return;
|
|
|
-
|
|
|
+ if (bounds.Width < 120 || bounds.Height < 120) return;
|
|
|
context.DrawRectangle(ThemeHelper.PanelBg, null, bounds);
|
|
|
|
|
|
+ RenderDynamic(context, bounds);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ════════════════════════════════════════════════════════
|
|
|
+ // 渲染
|
|
|
+ // ════════════════════════════════════════════════════════
|
|
|
+
|
|
|
+ private void RenderDynamic(DrawingContext context, Rect bounds)
|
|
|
+ {
|
|
|
+ var profile = Profile!;
|
|
|
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);
|
|
|
|
|
|
+ // 缓存布局
|
|
|
+ var designSize = new Size(960, 540);
|
|
|
+ if (_cachedLayout == null || _lastCanvasSize != designSize)
|
|
|
+ {
|
|
|
+ _cachedLayout = _layoutEngine.Calculate(profile, designSize);
|
|
|
+ _lastCanvasSize = designSize;
|
|
|
+ }
|
|
|
+
|
|
|
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);
|
|
|
- }
|
|
|
+ // 绘制管道 — 流动状态根据泵运行状态动态判断
|
|
|
+ var anyPumpRunning = _cachedLayout.Devices.Any(d =>
|
|
|
+ d.DeviceType == "Pump" && GetDigitalValue($"{d.DeviceId}.Run"));
|
|
|
|
|
|
- private bool AnyPumpRunning => Pump1Running || Pump2Running || Pump3Running || Pump4Running || Pump5Running;
|
|
|
+ foreach (var pipe in _cachedLayout.Pipes)
|
|
|
+ {
|
|
|
+ var from = new Point(pipe.From.X, pipe.From.Y);
|
|
|
+ var to = new Point(pipe.To.X, pipe.To.Y);
|
|
|
+ var flowing = pipe.IsFlowing && anyPumpRunning;
|
|
|
+ DrawPipe(context, origin, scale, from, to, flowing, 90);
|
|
|
+ }
|
|
|
|
|
|
- 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);
|
|
|
- }
|
|
|
+ // 绘制设备
|
|
|
+ foreach (var device in _cachedLayout.Devices)
|
|
|
+ {
|
|
|
+ var cx = device.Bounds.Center.X;
|
|
|
+ var cy = device.Bounds.Center.Y;
|
|
|
+
|
|
|
+ switch (device.DeviceType)
|
|
|
+ {
|
|
|
+ case "Tank":
|
|
|
+ DrawDynamicTank3D(context, origin, scale, cx, cy, device);
|
|
|
+ break;
|
|
|
+ case "Pump":
|
|
|
+ var running = GetDigitalValue($"{device.DeviceId}.Run");
|
|
|
+ var fault = GetDigitalValue($"{device.DeviceId}.Fault");
|
|
|
+ DrawDynamicPump3D(context, origin, scale, cx, cy, device.DeviceId, running, fault);
|
|
|
+ break;
|
|
|
+ case "Gauge":
|
|
|
+ var gVal = (double)GetAnalogValue($"{device.DeviceId}.Value");
|
|
|
+ var unit = device.Parameters.TryGetValue("Unit", out var gu) ? gu.ToString() ?? "" : "";
|
|
|
+ DrawDynamicInfo3D(context, origin, scale, cx, cy, device.DeviceId, $"{gVal:F1} {unit}");
|
|
|
+ break;
|
|
|
+ case "Sensor":
|
|
|
+ var sVal = (double)GetAnalogValue($"{device.DeviceId}.Value");
|
|
|
+ var su = device.Parameters.TryGetValue("Unit", out var su2) ? su2.ToString() ?? "" : "";
|
|
|
+ DrawDynamicInfo3D(context, origin, scale, cx, cy, device.DeviceId, $"{sVal:F1} {su}");
|
|
|
+ break;
|
|
|
+ case "Fan":
|
|
|
+ DrawDynamicFan3D(context, origin, scale, cx, cy, device);
|
|
|
+ break;
|
|
|
+ case "Valve":
|
|
|
+ DrawDynamicValve3D(context, origin, scale, cx, cy, device);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- 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));
|
|
|
+ // 标题 HUD
|
|
|
+ var hudP = new Rect(scene.X + 10, scene.Y + 10, 220 * scale, 50 * scale);
|
|
|
+ context.DrawRectangle(new SolidColorBrush(GetColor(ThemeHelper.SurfaceBg), 0.9), new Pen(ThemeHelper.Border, 1), hudP, 4 * scale);
|
|
|
+ DrawText(context, profile.Name, hudP.Position + new Point(12 * scale, 8 * scale), 13 * scale, ThemeHelper.TextPrimary, FontWeight.Bold);
|
|
|
+ DrawText(context, profile.Description, hudP.Position + new Point(12 * scale, 28 * scale), 10 * scale, ThemeHelper.TextTertiary, FontWeight.Bold);
|
|
|
|
|
|
- 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));
|
|
|
+ // 报警边框
|
|
|
+ if (GetDigitalValue("Alarm.Active"))
|
|
|
+ context.DrawRectangle(null, new Pen(ThemeHelper.Danger, 2), scene, 4);
|
|
|
}
|
|
|
|
|
|
- private void DrawChannel(DrawingContext context, Point origin, double scale, Point p, string name, double level)
|
|
|
+ private void DrawDynamicTank3D(DrawingContext context, Point origin, double scale, double designX, double designY, DeviceLayout device)
|
|
|
{
|
|
|
- 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);
|
|
|
- }
|
|
|
+ var rawLevel = GetAnalogValue($"{device.DeviceId}.Level");
|
|
|
+ var maxLevel = device.Parameters.TryGetValue("MaxLevel", out var ml) ? Convert.ToSingle(ml) : 10.0f;
|
|
|
+ if (maxLevel <= 0) maxLevel = 10.0f;
|
|
|
+ var levelPct = rawLevel / maxLevel * 100.0f;
|
|
|
|
|
|
- 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);
|
|
|
+ // 用 3D 盒子表示水箱
|
|
|
+ var w = 120.0;
|
|
|
+ var d = 100.0;
|
|
|
+ var h = 80.0;
|
|
|
+ var p = new Point(designX - w / 2, designY - d / 2);
|
|
|
+
|
|
|
+ DrawBox(context, origin, scale, p, w, d, h, Color.FromRgb(46, 59, 77), new SolidColorBrush(GetColor(ThemeHelper.Border)));
|
|
|
+
|
|
|
+ // 水位(百分比)
|
|
|
+ var waterH = h * Math.Clamp(levelPct, 0, 100) / 100.0;
|
|
|
+ if (waterH > 2)
|
|
|
+ DrawBox(context, origin, scale, new Point(p.X + 8, p.Y + 7), w - 16, d - 14, waterH,
|
|
|
+ Color.FromRgb(33, 150, 243), null, 0.62);
|
|
|
+
|
|
|
+ // 标签
|
|
|
+ var top = Iso(new Point(p.X + w * 0.52, p.Y + d * 0.22), h + 12, origin, scale);
|
|
|
+ DrawText(context, device.DeviceId, top + new Point(-30 * scale, -28 * scale), 12 * scale, ThemeHelper.TextPrimary, FontWeight.Bold);
|
|
|
+ DrawText(context, $"{rawLevel:F2}m ({levelPct:F0}%)", top + new Point(-30 * scale, -12 * scale), 11 * scale, ThemeHelper.Info, FontWeight.Bold);
|
|
|
}
|
|
|
|
|
|
- private void DrawCutawayWellWalls(DrawingContext context, Point origin, double scale, Point p, double width, double depth, double height)
|
|
|
+ private void DrawDynamicPump3D(DrawingContext context, Point origin, double scale, double designX, double designY, string name, bool running, bool fault)
|
|
|
{
|
|
|
- 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));
|
|
|
+ var p = new Point(designX, designY);
|
|
|
+ var status = fault
|
|
|
+ ? (Math.Sin(_faultFlash * Math.PI * 2) > 0 ? ThemeHelper.Danger : ThemeHelper.TextDisabled)
|
|
|
+ : (running ? ThemeHelper.Success : ThemeHelper.TextDisabled);
|
|
|
+
|
|
|
+ // 立管(垂直管道)
|
|
|
+ var basePoint = Iso(p, 10, origin, scale);
|
|
|
+ var topPoint = Iso(p, 90, origin, scale);
|
|
|
+ context.DrawLine(new Pen(new SolidColorBrush(Darken(StructureColor, 0.70)), 12 * scale), basePoint, topPoint);
|
|
|
+ context.DrawLine(new Pen(status, 4 * scale), basePoint, topPoint);
|
|
|
+
|
|
|
+ // 泵体(椭圆 + 圆角矩形)
|
|
|
+ var c = Iso(p, 30, origin, scale);
|
|
|
+ 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(GetColor(status)), Math.Max(1, 1.4 * scale)), body, radius * 0.42);
|
|
|
+ context.DrawEllipse(new SolidColorBrush(Lighten(StructureColor, 0.12)),
|
|
|
+ new Pen(new SolidColorBrush(GetColor(status)), Math.Max(1, scale)),
|
|
|
+ new Rect(c.X - radius, c.Y - radius * 1.7, radius * 2, radius * 0.78));
|
|
|
+
|
|
|
+ // 运行指示灯
|
|
|
+ context.DrawEllipse(new SolidColorBrush(running ? GetColor(ThemeHelper.Success) : GetColor(ThemeHelper.TextDisabled)),
|
|
|
+ null, new Rect(c.X - 3 * scale, c.Y - radius * 0.55, 6 * scale, 6 * scale));
|
|
|
+
|
|
|
+ DrawText(context, name, c + new Point(-12 * scale, radius * 1.3), 10 * scale, ThemeHelper.TextPrimary, FontWeight.Bold);
|
|
|
+
|
|
|
+ // 故障标记
|
|
|
+ if (fault)
|
|
|
+ DrawText(context, "!", c + new Point(radius + 2 * scale, -radius * 1.5), 14 * scale, ThemeHelper.Danger, FontWeight.Bold);
|
|
|
}
|
|
|
|
|
|
- private void DrawValveChamber(DrawingContext context, Point origin, double scale, Point p, double level)
|
|
|
+ private void DrawDynamicInfo3D(DrawingContext context, Point origin, double scale, double designX, double designY, string name, string valueText)
|
|
|
{
|
|
|
- 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);
|
|
|
+ // 用 3D 盒子表示仪表/传感器
|
|
|
+ var bw = 80.0;
|
|
|
+ var bd = 40.0;
|
|
|
+ var bh = 30.0;
|
|
|
+ var p = new Point(designX - bw / 2, designY - bd / 2);
|
|
|
+
|
|
|
+ DrawBox(context, origin, scale, p, bw, bd, bh, Color.FromRgb(46, 59, 77), new SolidColorBrush(GetColor(ThemeHelper.Border)));
|
|
|
+
|
|
|
+ // 面板上的文字
|
|
|
+ var top = Iso(new Point(p.X + bw * 0.5, p.Y + bd * 0.3), bh + 6, origin, scale);
|
|
|
+ DrawText(context, name, top + new Point(-20 * scale, -18 * scale), 9 * scale, ThemeHelper.TextTertiary, FontWeight.Bold);
|
|
|
+ DrawText(context, valueText, top + new Point(-24 * scale, -4 * scale), 11 * scale, ThemeHelper.Success, FontWeight.Bold);
|
|
|
}
|
|
|
|
|
|
- private void DrawCabinet(DrawingContext context, Point origin, double scale, Point p)
|
|
|
+ private void DrawDynamicFan3D(DrawingContext context, Point origin, double scale, double designX, double designY, DeviceLayout device)
|
|
|
{
|
|
|
- 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);
|
|
|
+ var running = GetDigitalValue($"{device.DeviceId}.Run");
|
|
|
+ var c = Iso(new Point(designX, designY), 34, origin, scale);
|
|
|
+ var r = 24 * scale;
|
|
|
+ var status = running ? GetColor(ThemeHelper.Info) : GetColor(ThemeHelper.TextDisabled);
|
|
|
+
|
|
|
+ context.DrawEllipse(PumpBodyBrush, 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, device.DeviceId, c + new Point(-10 * scale, r + 5 * scale), 11 * scale, ThemeHelper.TextPrimary, FontWeight.Bold);
|
|
|
}
|
|
|
|
|
|
- private void DrawRiser(DrawingContext context, Point origin, double scale, Point p, string label, bool running)
|
|
|
+ private void DrawDynamicValve3D(DrawingContext context, Point origin, double scale, double designX, double designY, DeviceLayout device)
|
|
|
{
|
|
|
- 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);
|
|
|
+ var isOpen = GetDigitalValue($"{device.DeviceId}.Open");
|
|
|
+ var c = Iso(new Point(designX, designY), 42, origin, scale);
|
|
|
+ var color = isOpen ? GetColor(ThemeHelper.Success) : GetColor(ThemeHelper.Danger);
|
|
|
+ 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, device.DeviceId, c + new Point(-10 * scale, s + 3 * scale), 10 * scale, ThemeHelper.TextSecondary, FontWeight.Bold);
|
|
|
}
|
|
|
|
|
|
- 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)
|
|
|
+ // ═══ 点击事件 ═══
|
|
|
+
|
|
|
+ protected override void OnPointerPressed(PointerPressedEventArgs e)
|
|
|
{
|
|
|
- DrawBox(context, origin, scale, p, w, d, h, Color.FromRgb(46, 59, 77), new SolidColorBrush(GetColor(ThemeHelper.Border)));
|
|
|
+ base.OnPointerPressed(e);
|
|
|
+ if (_cachedLayout == null) return;
|
|
|
|
|
|
- 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 pos = e.GetPosition(this);
|
|
|
+ // 反向映射到设计坐标
|
|
|
+ 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);
|
|
|
|
|
|
- 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);
|
|
|
+ // 简单近似:检查每个设备的设计坐标范围
|
|
|
+ foreach (var device in _cachedLayout.Devices)
|
|
|
+ {
|
|
|
+ var dc = Iso(device.Bounds.Center, 40, origin, scale);
|
|
|
+ var dist = Math.Sqrt(Math.Pow(pos.X - dc.X, 2) + Math.Pow(pos.Y - dc.Y, 2));
|
|
|
+ if (dist < 30 * scale)
|
|
|
+ {
|
|
|
+ if (DeviceClickedCommand?.CanExecute(device.DeviceId) == true)
|
|
|
+ {
|
|
|
+ DeviceClickedCommand.Execute(device.DeviceId);
|
|
|
+ e.Handled = true;
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- private void DrawStructure(DrawingContext context, Point origin, double scale, Point p, double w, double d, double h, string label)
|
|
|
+ // ═══ 等轴测投影 & 绘制辅助 ═══
|
|
|
+
|
|
|
+ private Point Iso(Point p, double z, Point origin, double scale) =>
|
|
|
+ new(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)
|
|
|
{
|
|
|
- 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);
|
|
|
+ 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 DrawBox(DrawingContext context, Point origin, double scale, Point p, double w, double d, double h, Color color, IBrush? stroke, double opacity = 1)
|
|
|
@@ -283,82 +394,16 @@ public class PumpStation3DControl : Control
|
|
|
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));
|
|
|
+ if (!isFlowing) return;
|
|
|
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));
|
|
|
+ var pt = new Point(a.X + (b.X - a.X) * t, a.Y + (b.Y - a.Y) * t);
|
|
|
+ context.DrawEllipse(new SolidColorBrush(Color.FromRgb(3, 169, 244)), null, new Rect(pt.X - 4 * scale, pt.Y - 4 * scale, 8 * scale, 8 * scale));
|
|
|
}
|
|
|
- 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)
|
|
|
{
|
|
|
@@ -378,18 +423,14 @@ public class PumpStation3DControl : Control
|
|
|
}
|
|
|
|
|
|
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(
|
|
|
+ private static Color Lighten(Color color, double amount) =>
|
|
|
+ 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);
|
|
|
}
|